summary refs log tree commit diff
path: root/.vim/lua/internal
diff options
context:
space:
mode:
authorRobert Günzler <r@gnzler.io>2022-10-14 20:57:03 +0200
committerRobert Günzler <r@gnzler.io>2022-10-14 21:01:15 +0200
commit2d2009413f8be624a7cca95cf702e32d9315cbef (patch)
treef024c1b5b27b886530e86221593e3cfdc3672d7e /.vim/lua/internal
parent6d7e524d3d7a71da2cea61b335a59e9544e2a6f7 (diff)
vim: move custom modules under internal/
Diffstat (limited to '.vim/lua/internal')
-rw-r--r--.vim/lua/internal/align.lua103
-rw-r--r--.vim/lua/internal/dap.lua162
-rw-r--r--.vim/lua/internal/exrc.lua49
-rw-r--r--.vim/lua/internal/hardmode.lua10
-rw-r--r--.vim/lua/internal/job.lua140
-rw-r--r--.vim/lua/internal/lsp.lua299
-rw-r--r--.vim/lua/internal/misc.lua210
-rw-r--r--.vim/lua/internal/snips.lua124
-rw-r--r--.vim/lua/internal/spell.lua9
-rw-r--r--.vim/lua/internal/statusline.lua255
10 files changed, 1361 insertions, 0 deletions
diff --git a/.vim/lua/internal/align.lua b/.vim/lua/internal/align.lua
new file mode 100644
index 0000000..5dcf7c0
--- /dev/null
+++ b/.vim/lua/internal/align.lua
@@ -0,0 +1,103 @@
+-- based on the ideas from:
+-- https://github.com/RRethy/nvim-align
+
+local M = {}
+
+function M.align_lines(pat, line1, line2, preview_ns, preview_bufnr)
+  local re = vim.regex(pat)
+  local bufnr = vim.api.nvim_get_current_buf()
+  local lines = vim.api.nvim_buf_get_lines(bufnr, line1 - 1, line2, false)
+  local newlines = {}
+  local preview_buf_line = 0
+
+  -- find the longest match
+  local max = -1
+  for _, line in pairs(lines) do
+    local s = re:match_str(line)
+    if s and max < s then max = s end
+  end
+
+  -- exit if nothing was found
+  if max == -1 then return error('nothing found') end
+
+  for i, line in pairs(lines) do
+    local s = re:match_str(line)
+    if s then
+      local rep = max - s
+      local changeset = {
+        string.sub(line, 1, s),
+        string.rep(' ', rep),
+        string.sub(line, s+1),
+      }
+      local newline = table.concat(changeset)
+
+      -- append to changse if not inside the preview callback
+      if not preview_ns then
+        newlines[#newlines+1] = newline
+      end
+
+      if preview_ns ~= nil then
+        -- set extmarks inside the live buffer
+        vim.api.nvim_buf_set_extmark(bufnr, preview_ns, line1+i - 2, 0,
+          {
+            hl_mode = 'combine',
+            virt_text_pos = 'overlay',
+            virt_text = {
+              {changeset[1]},
+              {changeset[2], 'Substitute'},
+              {changeset[3]},
+            },
+          })
+
+        -- modify preview buffer
+        if preview_bufnr ~= nil then
+          local prefix = string.format('|%d| ', line1 + i - 1)
+          vim.api.nvim_buf_set_lines(preview_bufnr, preview_buf_line, preview_buf_line, false, { prefix .. newline })
+          vim.api.nvim_buf_add_highlight(preview_bufnr, preview_ns, 'Substitute', preview_buf_line, #prefix + s, #prefix + s + #changeset[2])
+          preview_buf_line = preview_buf_line + 1
+        end
+      end
+    end
+  end
+
+  -- only change buffer when not previewing
+  if not preview_ns then
+    -- exit if nothing was changed
+    if #newlines == 0 then return error('nothing changed') end
+    vim.api.nvim_buf_set_lines(bufnr, line1 - 1, line2, 0, newlines)
+    return 0
+  end
+
+  if preview_ns ~= nil then
+    -- open preview buffer only if there is more than a single line of change
+    return (#preview_buf_line > 1) and 2 or 1
+  end
+end
+
+function M.align(pat)
+    local top, bot = vim.fn.getpos("'<"), vim.fn.getpos("'>")
+    M.align_lines(pat, top[2]-1, bot[2])
+    vim.fn.setpos("'<", top)
+    vim.fn.setpos("'>", bot)
+end
+
+local function aligncmd(opts, preview_ns, preview_bufnr)
+  return M.align_lines(opts.fargs[1], opts.line1, opts.line2, preview_ns, preview_bufnr)
+end
+
+local default_opts = {
+  bindings = true
+}
+
+function M.setup(opts)
+  opts = vim.tbl_extend('keep', opts or {}, default_opts)
+
+  vim.api.nvim_create_user_command('SimpleAlign', aligncmd,
+    {nargs = 1, range = '%', preview = aligncmd})
+
+  if opts.bindings then
+    vim.keymap.set('v', '<enter>', ':SimpleAlign ')
+  end
+end
+
+return M
diff --git a/.vim/lua/internal/dap.lua b/.vim/lua/internal/dap.lua
new file mode 100644
index 0000000..aa419ea
--- /dev/null
+++ b/.vim/lua/internal/dap.lua
@@ -0,0 +1,162 @@
+-- vim: fdm=marker
+
+local dap = require('dap')
+dap.defaults.fallback.terminal_win_cmd = 'belowright 10new'
+dap.defaults.fallback.terminal_win_cmd = 'belowright 10new'
+
+-- open repl when session starts
+dap.listeners.after['event_initialized']['me'] = function()
+  dap.repl.toggle()
+end
+
+-- signs
+vim.fn.sign_define({{name='DapBreakpoint', text='🞱', texthl='DapBreakpoint', linehl='DapBreakpointLn'}})
+vim.fn.sign_define({{name='DapStopped', text='→', texthl='DapStopped', linehl='DapStoppedLn'}})
+
+-- keymaps
+vim.keymap.set('n', '<leader>dd', dap.toggle_breakpoint, {desc='toggle breakpoint'}) -- convenience
+vim.keymap.set('n', '<leader>db', dap.toggle_breakpoint, {desc='toggle breakpoint'})
+vim.keymap.set('n', '<leader>dc', dap.continue, {desc='continue'})
+vim.keymap.set('n', '<leader>do', dap.step_over, {desc='step over'})
+vim.keymap.set('n', '<leader>di', dap.step_into, {desc='step into'})
+vim.keymap.set('n', '<leader>dO', dap.step_out, {desc='step out'})
+vim.keymap.set('n', '<leader>dR', dap.repl.toggle, {desc='toggle REPL'})
+vim.keymap.set('n', '<leader>dL', dap.run_last, {desc='run last'})
+vim.keymap.set('n', '<leader>dt', dap.terminate, {desc='terminate'})
+
+local widgets = require('dap.ui.widgets')
+vim.keymap.set('n', '<leader>dh', widgets.hover, {desc='hover'})
+vim.keymap.set('n', '<leader>dB', require('telescope').extensions.dap.list_breakpoints, {desc='list breakpoints'})
+
+-- Common
+local bin_from_var_or_pick = function()
+  local ok, retval = pcall(vim.api.nvim_buf_get_var, 0, 'dap_bin')
+  if ok then return retval end
+  local bin = nil
+  vim.ui.input({prompt = 'DAP Binary: '}, function(choice) bin = choice end)
+  return bin
+end
+
+-- Go {{{
+dap.adapters.go_dlv_local = function(callback, config) -- {{{
+  local stdout = vim.loop.new_pipe()
+  local handle
+  local pid_or_err
+  local port = 38697
+  local opts = {
+    stdio = {nil, stdout},
+    args = {'dap', '-l', '127.0.0.1:' .. port},
+    detached = true,
+  }
+  handle, pid_or_err = vim.loop.spawn("dlv", opts, function(exit)
+    stdout:close()
+    handle:close()
+    if exit ~= 0 then vim.notify('dlv exited with exit code ' .. exit) end
+  end)
+  assert(handle, 'Error running dlv: ' .. tostring(pid_or_err))
+  stdout:read_start(function(err, chunk)
+    assert(not err, err)
+    if chunk then vim.schedule(function() require('dap.repl').append(chunk) end) end
+  end)
+  vim.defer_fn(function() callback({type = 'server', host = '127.0.0.1', port = port}) end, 100)
+end -- }}}
+
+dap.adapters.go_dlv_remote = {
+  type = 'server',
+  host = '127.0.0.1',
+  port = 38697,
+}
+
+dap.configurations.go = {
+  {
+    name = 'Debug (local)',
+    type = 'go_dlv_local',
+    request = 'launch',
+    program = '${file}',
+  },
+  {
+    name = 'Debug (remote)',
+    type = 'go_dlv_remote',
+    request = 'launch',
+    mode = 'exec',
+    program = bin_from_var_or_pick,
+  },
+}
+-- end of Go }}}
+
+-- C/C++/Rust {{{
+-- dap.adapters.cppdbg = {
+--   id = 'cppdbg',
+--   type = 'executable',
+--   command = vim.env.HOME .. '/.local/share/nvim/dap/ms-vscode.cpptools/extension/debugAdapters/bin/OpenDebugAD7',
+-- }
+dap.adapters.lldb = {
+  type = 'executable',
+  command = vim.trim(vim.fn.system('command -v lldb-vscode')),
+  name = 'lldb',
+}
+
+-- dap.configurations.c = {
+--   {
+--     name = 'Launch binary',
+--     type = 'cppdbg',
+--     request = 'launch',
+--     program = function()
+--       local file = vim.g.dapbin
+--       if file then return file end
+--       -- TODO: assuming meson
+--       file = vim.trim(vim.fn.system(
+--         [[jq -r '..|select(.type?=="executable")|.filename|.[0]' ]] ..
+--         vim.lsp.buf.list_workspace_folders()[1] ..
+--         [[/build/meson-info/intro-targets.json]]
+--       ))
+--       local fd = vim.loop.fs_open(file, "r", 438)
+--       if file and fd then
+--         vim.loop.fs_close(fd)
+--         return file
+--       end
+--       vim.ui.input('binary: ', function(result) file = result end)
+--       return file
+--     end,
+--     cwd = '${workspaceFolder}',
+--     stopOnEntry = true,
+--   },
+--   {
+--     name = 'Attach to gdbserver',
+--     type = 'cppdbg',
+--     request = 'launch',
+--     MIMode = 'gdb',
+--     miDebuggerServerAddress = 'localhost:1234',
+--     miDebuggerPath = '/usr/bin/gdb',
+--     cwd = '${workspaceFolder}',
+--     program = function()
+--       local file = vim.g.dapbin
+--       if file then return file end
+--       vim.ui.input('binary: ', function(result) file = result end)
+--       return file
+--     end,
+--   },
+-- }
+
+dap.configurations.c = {
+  {
+    name = 'Launch',
+    type = 'lldb',
+    request = 'launch',
+    program = bin_from_var_or_pick,
+    args = {},
+    cwd = '${workspaceFolder}',
+    stopOnEntry = false,
+  },
+  {
+    name = 'Attach',
+    type = 'lldb',
+    request = 'attach',
+    pid = require('dap.utils').pick_process,
+    args = {},
+  },
+}
+dap.configurations.cpp = dap.configurations.c
+dap.configurations.rust = dap.configurations.c
+
+-- end of C/C++/Rust }}}
diff --git a/.vim/lua/internal/exrc.lua b/.vim/lua/internal/exrc.lua
new file mode 100644
index 0000000..6b7742b
--- /dev/null
+++ b/.vim/lua/internal/exrc.lua
@@ -0,0 +1,49 @@
+local root_pattern = require('lspconfig.util').root_pattern
+
+local M = {
+  config = {
+    filenames = {'.nvimrc'}
+  }
+}
+
+M.setup = function (opt)
+  vim.validate {
+    filenames = { opt.filenames, 'table', true },
+  }
+
+  M.config = vim.tbl_extend('force', M.config, opt)
+
+  local group = vim.api.nvim_create_augroup('ExrcLoad', { clear = true })
+  vim.api.nvim_create_autocmd('VimEnter', {
+    callback = M.load,
+    group = group,
+  })
+end
+
+local load = function (root, filename)
+  local fpath = root .. '/' .. filename
+  if root and vim.loop.fs_stat(fpath) then
+    vim.cmd('luafile ' .. fpath)
+    vim.notify('[exrc] loaded')
+  else
+    error(string.format('[exrc] %s not found', fpath))
+  end
+end
+
+M.load = function ()
+  local root = vim.fn.getcwd()
+  for _, fname in ipairs(M.config.filenames) do
+    local ok = false
+    ok, _ = pcall(load, root, fname)
+    if ok then return true end
+
+    -- try harder by searching up the dir tree
+    root = root_pattern(fname)(root)
+    ok, _ = pcall(load, root, fname)
+    if ok then return true end
+  end
+
+  return false
+end
+
+return M
diff --git a/.vim/lua/internal/hardmode.lua b/.vim/lua/internal/hardmode.lua
new file mode 100644
index 0000000..cc71baf
--- /dev/null
+++ b/.vim/lua/internal/hardmode.lua
@@ -0,0 +1,10 @@
+-- hard mode means we're not using the arrow keys!
+
+local bail = function()
+  vim.cmd [[silent! echohl ErrorMsg | echo "HARD MODE: hjkl operation only" | echohl None]]
+end
+
+vim.keymap.set({'n', 'v', 'i'}, '<up>',      bail)
+vim.keymap.set({'n', 'v', 'i'}, '<down>',    bail)
+vim.keymap.set({'n', 'v', 'i'}, '<left>',    bail)
+vim.keymap.set({'n', 'v', 'i'}, '<right>',   bail)
diff --git a/.vim/lua/internal/job.lua b/.vim/lua/internal/job.lua
new file mode 100644
index 0000000..f9f7bf3
--- /dev/null
+++ b/.vim/lua/internal/job.lua
@@ -0,0 +1,140 @@
+local Job = require ('plenary.job')
+local spinner_ok, spinner = pcall(require, 'spinner.core')
+
+local M = {
+  current_jobs = {}
+}
+
+local strip_ansi = function(line)
+  return line:gsub(string.char(27) .. '[[0-9;]*m', '')
+end
+
+--create a new job and register
+--@return ~/.local/share/nvim/site/pack/packer/start/plenary.nvim/lua/plenary/job.lua
+function M.jobstart(opts)
+  opts = opts or {}
+
+  -- default options
+  -- opts.enable_recording = true
+  opts.enable_spinner = (opts.enable_spinner or true) and spinner_ok
+  opts.populate_quickfix = opts.populate_quickfix or true
+
+  local title
+  if opts.format_title then
+    title = opts.format_title(opts.command, opts.args)
+    opts.format_title = nil
+  else
+    title = opts.command
+  end
+
+  opts.on_start = vim.schedule_wrap(function(j)
+    table.insert(M.current_jobs, j.pid, j)
+
+    if opts.populate_quickfix then
+      -- clear quickfix
+      vim.fn.setqflist({}, 'r')
+    end
+
+    if opts.enable_spinner then
+      spinner.on_attach(j.pid, title, vim.fn.bufnr())
+      spinner.on_progress('begin', j.pid, j.pid)
+    end
+  end)
+
+  opts.on_exit = vim.schedule_wrap(function(j, _)
+    table.remove(M.current_jobs, j.pid)
+
+    if opts.populate_quickfix then
+      vim.cmd([[doautocmd QuickFixCmdPost]])
+      -- vim.fn.setqflist({}, 'r', {
+      --   lines = {},
+      --   id = j.pid,
+      -- })
+    end
+
+    if opts.enable_spinner then
+      spinner.on_progress('end', j.pid, j.pid)
+      spinner.on_exit(nil, nil, j.pid)
+    end
+  end)
+
+  opts.on_stdout = vim.schedule_wrap(function(error, data, j)
+    if error then
+      vim.notify('error: ' .. error)
+      return
+    end -- handle error?
+
+    if opts.populate_quickfix and data then
+      vim.fn.setqflist({}, 'a', {
+        title = title,
+        lines = {strip_ansi(data)},
+        efm = '%m',
+        id = j.pid,
+      })
+    end
+  end)
+
+  opts.on_stderr = vim.schedule_wrap(function(error, data, j)
+    if error then
+      vim.notify('error: ' .. error)
+      return
+    end -- handle error?
+
+    if opts.populate_quickfix and data then
+      vim.fn.setqflist({}, 'a', {
+        title = title,
+        lines = {strip_ansi(data)},
+        efm = '%m',
+        id = j.pid,
+      })
+      vim.cmd([[doautocmd QuickFixCmdPost]])
+    end
+  end)
+
+  -- run vim.fn.expand() on all args
+  for i=1, #opts.args do
+    opts.args[i] = vim.fn.expandcmd(opts.args[i])
+  end
+
+  local j = Job:new(opts)
+  j:start()
+
+  return j
+end
+
+function M.make(extra_args)
+  local makeprg = vim.fn.expandcmd(vim.opt.makeprg:get())
+  -- split makeprg into command + args
+  local args = vim.split(makeprg, ' ')
+  local command = args[1]
+  table.remove(args, 1)
+
+  local cwd = vim.fn.expand('%:p:h')
+
+  if not (string.match(makeprg, 'make') == nil) then
+    -- detect makefile
+    cwd = require('lspconfig.util').root_pattern('Makefile')(cwd)
+  end
+
+  table.foreach(extra_args or {}, function(_, v) table.insert(args, v) end)
+
+  return M.jobstart {
+    command = command,
+    args = args,
+    cwd = cwd,
+  }
+end
+
+function M.sh(command_string)
+  return M.jobstart {
+    command = vim.env.SHELL,
+    args = {'-c', command_string},
+
+    -- strip 'sh -c' from the title (used by progress reporting etc.)
+    format_title = function(_, args)
+      return require('plenary.collections.py_list')(args):slice(2,#args):join(' ')
+    end
+  }
+end
+
+return M
diff --git a/.vim/lua/internal/lsp.lua b/.vim/lua/internal/lsp.lua
new file mode 100644
index 0000000..669c2c7
--- /dev/null
+++ b/.vim/lua/internal/lsp.lua
@@ -0,0 +1,299 @@
+local lsp         = require('lspconfig')
+local lspstatus   = require('lsp-status')
+
+local my_attach = function(client, bufnr)
+  if vim.opt.diff:get() then
+    vim.notify('not running LSP client in diff mode', vim.log.levels.WARN)
+    vim.lsp.stop_client()
+    return
+  end
+
+  local group = vim.api.nvim_create_augroup('LspOnAttach', {})
+  vim.api.nvim_create_autocmd('CursorHold', {
+    buffer = bufnr,
+    group = group,
+    callback = function()
+      if not _G.diagnostic_hidden then
+        vim.diagnostic.open_float(nil, {
+          focusable = false,
+          close_events = {'BufLeave', 'CursorMoved', 'InsertEnter', 'FocusLost'},
+          border = _G.floating_win_border,
+          source = 'always',
+          prefix = ' ',
+          scope = 'cursor',
+        })
+      end
+    end,
+  })
+
+  lspstatus.on_attach(client)
+  if client.supports_method('textDocument/documentSymbol') then
+    require('nvim-navic').attach(client, bufnr)
+    -- require('aerial').on_attach(client, bufnr)
+    -- vim.keymap.set('n', 'g0', vim.lsp.buf.document_symbol, {buffer = bufnr})
+  end
+  if client.supports_method('workspace/symbol') then
+    vim.keymap.set('n', 'gW'        , vim.lsp.buf.workspace_symbol, {buffer = bufnr, desc = 'goto symbol'})
+    vim.keymap.set('n', ';s'        , require('telescope.builtin').lsp_workspace_symbols, {desc='lsp workspace symbols'})
+  end
+  if client.supports_method('textDocument/definition') then
+    vim.keymap.set('n', '<C-]>'     , vim.lsp.buf.definition, {buffer = bufnr, desc = 'goto definition'})
+    vim.keymap.set('n', 'gd'        , vim.lsp.buf.definition, {buffer = bufnr, desc = 'goto definition'})
+    vim.keymap.set('n', 'gR'        , vim.lsp.buf.references, {buffer = bufnr, desc = 'goto references'})
+    vim.keymap.set('n', ';R'        , require('telescope.builtin').lsp_references, {desc='lsp references'})
+  end
+  if client.supports_method('textDocument/hover') then
+    vim.keymap.set('n', 'K'         , vim.lsp.buf.hover, {buffer = bufnr, desc = 'hover'})
+  end
+  if client.supports_method('textDocument/rename') then
+    vim.keymap.set('n', 'grr'       , vim.lsp.buf.rename, {buffer = bufnr, desc = 'rename'})
+  end
+  if client.supports_method('signatureHelp') then
+    vim.keymap.set('n', '<c-h>'     , vim.lsp.buf.signature_help, {buffer = bufnr, desc = 'signature help'})
+  end
+  if client.supports_method('textDocument/formatting') then
+    vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr()')
+    vim.keymap.set('n', '<leader>f' , vim.lsp.buf.format, {buffer = bufnr, desc = 'format'})
+    vim.api.nvim_create_autocmd('BufWritePre', {
+      buffer = bufnr,
+      group = group,
+      callback = function() require('internal.lsp').format(vim.fn.expand('<afile>:p')) end,
+    })
+  end
+  if client.supports_method('textDocument/typeDefinition') then
+    vim.keymap.set('n', 'gT'        , vim.lsp.buf.type_definition, {buffer = bufnr, desc = 'goto typedef'})
+  end
+  if client.supports_method('textDocument/declaration') then
+    vim.keymap.set('n', 'gD'        , vim.lsp.buf.declaration, {buffer = bufnr, desc = 'goto declaration'})
+  end
+  if client.supports_method('textDocument/implementation') then
+    vim.keymap.set('n', 'gI'        , vim.lsp.buf.implementation, {buffer = bufnr, desc = 'goto implementation'})
+  end
+  if client.supports_method('textDocument/codeAction') then
+    vim.keymap.set('n', 'ga', vim.lsp.buf.code_action, {buffer = bufnr, desc = 'code action'})
+  end
+
+  if client.supports_method('textDocument/documentHighlight') then
+    vim.keymap.set('n', '<leader>8', vim.lsp.buf.document_highlight, {buffer = bufnr, desc = 'document highlight'})
+    -- vim.keymap.set('n', '', vim.lsp.buf.clear_references, {buffer = bufnr, desc = 'clear references'})
+    vim.api.nvim_create_autocmd('CursorMoved', {
+      callback = vim.lsp.buf.clear_references,
+      buffer = bufnr,
+    })
+  end
+end
+
+local my_exit = function(_, _, _)
+end
+
+local capabilities = function()
+  local caps = vim.lsp.protocol.make_client_capabilities()
+  -- enable lsp-based snippets
+  caps.textDocument.completion.completionItem.snippetSupport = true
+  caps.textDocument.completion.completionItem.resolveSupport = {
+    properties = {
+      'documentation',
+      'detail',
+      'additionalTextEdits',
+    }
+  }
+  -- add window/workDoneProgress capability
+  caps = vim.tbl_extend('keep', caps or {}, lspstatus.capabilities)
+  return caps
+end
+
+local servers = {
+  ccls = { disabled = true,
+    root_dir = lsp.util.root_pattern('.ccls','meson.build','.git'),
+    init_options = {
+      compilationDatabaseDirectory = "build",
+      index = { threads = 0 },
+      completion = {
+        filterAndSort = false,
+      },
+      clang = { excludeArgs = { '-frounding-math' } },
+    },
+  },
+  clangd = {
+    root_dir = lsp.util.root_pattern('.ccls','meson.build','.git'),
+  },
+  elixirls = {
+    cmd = { 'elixir-ls' },
+    settings = {
+      elixirLS = {
+        dialyzerEnabled = false,
+      }
+    }
+  },
+  gopls = {
+    settings = {
+      gopls = {
+        analyses = {
+          --  composites = false,
+          fieldalignment = true,
+          nilness = true,
+          shadow = false,
+          unusedparams = true,
+          unusedwrite = true,
+        },
+        gofumpt = true,
+        -- hoverKind = 'Structured',
+      }
+    }
+  },
+  sumneko_lua = {
+    cmd = { 'lua-language-server' },
+    settings = {
+      Lua = {
+        runtime = {
+          version = 'LuaJIT',
+          path = vim.split(package.path, ';'),
+        },
+        diagnostics = {
+          globals = {
+            '_G',
+            'assert',
+            'error',
+            'os',
+            'package',
+            'pairs', 'ipairs',
+            'pcall',
+            'require',
+            'string',
+            'table',
+            'type',
+            'vim',
+          },
+          neededFileStatus = {
+            ['code-style-check'] = 'Any',
+          }
+        },
+        format = {
+          enable =  true,
+          defaultConfig = {
+            indent_style = 'space',
+            indent_size = '2',
+          }
+        },
+        workspace = {
+          -- library = vim.api.nvim_get_runtime_file("", true),
+          library = {
+            [vim.env.VIMRUNTIME .. '/lua'] = true,
+            [vim.env.VIMRUNTIME .. '/lua/vim/lsp'] = true,
+            [vim.fn.stdpath('config') .. '/lua'] = true,
+          }
+        },
+        telemetry = {
+          enable = false,
+        },
+      },
+    },
+  },
+  rust_analyzer = {
+    cmd = { 'env', 'CARGO_TARGET_DIR=/tmp', 'RUST_SRC_PATH=/usr/src/rustlib/library', 'rust-analyzer' },
+    settings = {
+      ['rust-analyzer'] = {
+        checkOnSave = {
+          command = 'clippy'
+        }
+      }
+    },
+  },
+  tsserver = { disabled = true,
+    cmd = {
+      'toolbox', 'run', '--',
+      'sh', '-c',
+      '. /etc/profile.d/nvm.sh && typescript-language-server --stdio'
+    },
+  },
+  zls = { disabled = true,
+  },
+  zk = { disabled = true,
+    root_dir = lsp.util.root_pattern('.zk'),
+  },
+}
+
+local setup = function()
+  -- configure floating win handlers
+  vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(
+    vim.lsp.handlers.hover, { border = _G.floating_win_border })
+  vim.lsp.handlers['textDocument/signature_help'] = vim.lsp.with(
+    vim.lsp.handlers.signature_help, { border = _G.floating_win_border })
+
+  local caps = capabilities()
+  for server, opts in pairs(servers) do
+    if not opts.disabled then
+      opts = vim.tbl_extend('error', opts, {
+        on_attach    = my_attach,
+        on_exit      = my_exit,
+        capabilities = caps,
+      })
+      -- lsp-status custom handlers
+      local ok, lspstatus_ext = pcall(lspstatus.extensions[server])
+      if ok then
+        opts = vim.tbl_extend('error', opts, {
+          handlers = lspstatus_ext.setup(),
+        })
+      end
+      lsp[server].setup(opts)
+    end
+  end
+end
+
+local format = function(afile)
+  if vim.g.nofmt then return end
+  if not string.match(afile, '^/home/robert/devel/upstream') then
+    vim.lsp.buf.format()
+    if string.match(afile, '.go$') then
+      vim.lsp.buf.code_action({ only = {'source.organizeImports'} })
+    end
+    -- vim.notify('%#MoreMsg#󰃢%#Italic# fmt')
+    vim.notify('%#MoreMsg#󰃢%#Italic# fmt')
+  end
+end
+
+local symbols = {
+  -- 󰆼  󱆃
+  Array = '󰨾 ',
+  Boolean = '󰦍 ',
+  Class = '󰙀 ',
+  Color = '󰉦 ',
+  Constant = ' ',
+  Constructor = '󱇿 ',
+  Enum = ' ',
+  EnumMember = ' ',
+  Event = '󱐋 ',
+  -- Field = '󰽜 ',
+  Field = '󰆈 ',
+  File = '󰈔 ',
+  Folder = '󰝰 ',
+  Function = ' ',
+  Interface = '󱦜 ',
+  Key = '󰌋 ',
+  Keyword = '󰓽 ',
+  Method = '󰒓 ',
+  Module = '󰏖 ',
+  Namespace = '󰆧 ',
+  Null = '󰎣 ',
+  Object = '󰘦 ',
+  Operator = '󱓉 ',
+  Property = '󰐱 ',
+  Package = '󰏗 ',
+  Reference = '󰌹 ',
+  Snippet = '󰯁 ',
+  Struct = '󰙅 ',
+  Text = '󰉿 ',
+  TypeParameter = '󰌨 ',
+  Unit = '󰠱 ',
+  Value = '󰎠 ',
+  Variable = '󱄑 ',
+}
+
+return {
+  my_attach    = my_attach,
+  my_exit      = my_exit,
+  capabilities = capabilities,
+  setup        = setup,
+  format       = format,
+  symbols       = symbols,
+}
diff --git a/.vim/lua/internal/misc.lua b/.vim/lua/internal/misc.lua
new file mode 100644
index 0000000..5a2ae21
--- /dev/null
+++ b/.vim/lua/internal/misc.lua
@@ -0,0 +1,210 @@
+-- misc stuff implemented in lua
+
+local M = {}
+
+function M.do_under_cursor(obj, cb)
+  return cb(vim.fn.expand(vim.fn.expand(obj)))
+end
+
+function M.word_under_cursor(cb)
+  return M.do_under_cursor('<cword>', cb)
+end
+
+function M.expr_under_cursor(cb)
+  return M.do_under_cursor('<cexpr>', cb)
+end
+
+function M.file_under_cursor(cb)
+  return M.do_under_cursor('<cfile>', cb)
+end
+
+function M.open_under_cursor(cmd, detect_cmd)
+  return M.file_under_cursor(function(txt)
+    txt = vim.trim(txt)
+    if not cmd and detect_cmd then
+      if vim.regex([[^http.*$]]):match_str(txt) then
+        cmd = 'url-launcher'
+      end
+      if vim.regex([[^(\.\./|[\w\d_/\-])*(\.[\w\d]+)?$]]):match_str(txt) then
+        cmd = ':edit'
+      end
+      vim.notify("open_under_cursor: detected command " .. txt .. " => " .. (cmd or "<nil>"))
+    end
+    if not cmd then
+      vim.fn.inputsave()
+      vim.ui.input('open with: ', function(result) cmd = result end)
+      vim.fn.inputrestore()
+      if not cmd then return end
+    end
+    -- detect vim command
+    if cmd:sub(1, 1) == ":" then
+      vim.cmd(cmd:sub(2) .. ' ' .. txt)
+    else
+      vim.loop.spawn(cmd, {args = {txt}})
+    end
+  end)
+end
+
+local function get_visual_selection()
+  local top = vim.fn.getpos("'<")
+  top = { ln = top[2], col = top[3] }
+  local bot = vim.fn.getpos("'>")
+  bot = { ln = bot[2], col = bot[3] }
+  local lines = vim.api.nvim_buf_get_lines(0, (top.ln - 1), bot.ln, false)
+  if vim.opt.selection:get() == 'inclusive' then
+    lines[#lines] = lines[#lines]:sub(1, bot.col)
+  else
+    lines[#lines] = lines[#lines]:sub(1, (bot.col -1))
+  end
+  lines[1] = lines[1]:sub(top.col)
+  return top, bot, lines
+end
+
+function M.sort_lines(_, preview_ns, preview_bufnr)
+  local bufnr = vim.api.nvim_get_current_buf()
+
+  local top, bot, lines = get_visual_selection()
+  for i=1, #lines do
+    lines[i] = vim.fn.join(vim.fn.sort(vim.fn.split(lines[i], " ")))
+  end
+
+  if not preview_ns then
+    vim.api.nvim_buf_set_lines(bufnr, top.ln-1, bot.ln, false, lines)
+    vim.notify('no preview')
+    return 0
+  end
+
+  -- inccommand preview
+  if preview_ns ~= nil then
+    for i, line in ipairs(lines) do
+      vim.api.nvim_buf_set_extmark(bufnr, preview_ns, top.ln+i - 2, 0, {
+        hl_mode = 'combine',
+        virt_text_pos = 'overlay',
+        virt_text = {{line, 'Substitute'}},
+      })
+
+      if preview_bufnr ~= nil then
+        local prefix = string.format('|%d| ', top.ln + i - 1)
+        vim.api.nvim_buf_set_lines(preview_bufnr, i-1, -1, false, { prefix .. line })
+        vim.api.nvim_buf_add_highlight(preview_bufnr, preview_ns, 'Substitute', i, #prefix, #prefix+#line)
+      end
+    end
+    return (#lines > 1 and 2 or 1)
+  end
+end
+
+local telescope_builtin = require('telescope.builtin')
+local telescope_actions = require('telescope.actions')
+local telescope_action_state = require('telescope.actions.state')
+
+local get_searchdirs = function(dirs)
+  -- skip asking if we're looking at known filetypes
+  local ft = vim.api.nvim_buf_get_option(0, 'filetype')
+  if not dirs and (ft == 'dirbuf' or ft == 'alpha') then
+    dirs = '%:p:h'
+  end
+  if not dirs then
+    vim.ui.input('Search directories: ', function(result) dirs = result end)
+  end
+  -- default to something reasonable
+  if not dirs then dirs = '%:p:h' end
+  return vim.split(vim.fn.expand(dirs), ',')
+end
+
+function M.live_grep(dirs, opts, on_choice)
+  opts = opts or {}
+  local search_dirs = get_searchdirs(dirs)
+  opts.search_dirs = search_dirs
+  opts.prompt_title = 'Grep: '.. table.concat(search_dirs)
+  if on_choice then
+    opts.attach_mappings = function(prompt_bufnr)
+      telescope_actions.select_default:replace(function()
+        local selection = telescope_action_state.get_selected_entry()
+        if selection == nil then return end
+        telescope_actions.close(prompt_bufnr)
+        on_choice(selection.path)
+      end)
+      return true
+    end
+  end
+  telescope_builtin.live_grep(opts)
+end
+
+function M.find_files(dirs, opts, on_choice)
+  if dirs == 'plugins' then dirs = vim.fn.stdpath('data') .. '/site/pack/' end
+
+  opts = opts or {}
+  local search_dirs = dirs
+  if type(dirs) == 'string' then search_dirs = get_searchdirs(dirs) end
+  opts.search_dirs = search_dirs
+  opts.prompt_title = 'Find: '.. table.concat(search_dirs)
+  if on_choice then
+    opts.attach_mappings = function(prompt_bufnr)
+      telescope_actions.select_default:replace(function()
+        local selection = telescope_action_state.get_selected_entry()
+        if not selection then
+          vim.notify('Nothing selected', vim.log.levels.WARN)
+          return
+        end
+        telescope_actions.close(prompt_bufnr)
+        on_choice(selection.path)
+      end)
+      return true
+    end
+  end
+  telescope_builtin.find_files(opts)
+end
+
+
+function M.fold_block() -- {{{
+  local Comment = require('Comment.api')
+  local m = vim.api.nvim_buf_get_mark
+  local inital_cusor_pos = vim.api.nvim_win_get_cursor(0)
+  local spos, epos = m(0, '<'), m(0, '>')
+
+  -- do start of selection
+  vim.api.nvim_win_set_cursor(0, spos)
+  Comment.insert_linewise_eol()
+  vim.api.nvim_feedkeys('\\<esc>', 'ni', true)
+  local sln = vim.api.nvim_get_current_line()
+  vim.api.nvim_set_current_line(sln .. '{{{')
+
+  -- do end of selection
+  vim.api.nvim_win_set_cursor(0, epos)
+  Comment.insert_linewise_eol()
+  vim.api.nvim_feedkeys('\\<esc>', 'ni', true)
+  local eln = vim.api.nvim_get_current_line()
+  vim.api.nvim_set_current_line(eln .. '}}}')
+
+  -- restore initial cursor postition
+  vim.api.nvim_win_set_cursor(0, inital_cusor_pos)
+end -- }}}
+
+function M.link_preview()
+  local link = vim.fn.expand('<cfile>')
+  if not link then return end
+
+  local buf = vim.api.nvim_get_current_buf()
+  local ln = (vim.api.nvim_win_get_cursor(0)[1]) - 1
+
+  local j = require('job').jobstart({
+    format_title = function() return "link-preview"  end,
+    command = vim.env.SHELL,
+    args = {'-c', [[ curl -sSfL ]] .. link .. [[ | grep -iPo '(?<=property="og:title" content=")([^\"]+)(?=")' ]]},
+    populate_quickfix = false,
+    enable_recording = true,
+  })
+
+  j:after_success(vim.schedule_wrap(function(j, code, _)
+      local results = j:result()
+      if not code == 0 or #results == 0 then return end
+
+      local ns = vim.api.nvim_create_namespace('')
+      vim.api.nvim_buf_set_extmark(buf, ns, ln, 0, {
+        virt_text = {{results[1], 'Error'}},
+        virt_text_pos = 'eol'
+      })
+  end))
+end
+
+return M
diff --git a/.vim/lua/internal/snips.lua b/.vim/lua/internal/snips.lua
new file mode 100644
index 0000000..318c9f7
--- /dev/null
+++ b/.vim/lua/internal/snips.lua
@@ -0,0 +1,124 @@
+
+local ls = require('luasnip')
+local types = require('luasnip.util.types')
+
+vim.api.nvim_set_hl(0, 'LuasnipIndicator', {
+  fg = vim.g.terminal_color_15,
+  bg = vim.g.terminal_color_1,
+  italic = true,
+  nocombine = true,
+})
+
+-- autowrap: wraps the function in top and bot if there is any content
+local autowrap = function(top, bot, inner)
+  local autoinsert = function(args, _, _, wrap_with)
+    local nodes = {}
+    if vim.trim(table.concat(args[1] or {})) ~= "" then
+      table.insert(nodes, wrap_with)
+    end
+    return ls.sn(nil, nodes)
+  end
+
+  local argnodes = {}
+  local maxpos = -1
+  for _, e in ipairs(inner) do
+    if e.pos ~= nil then
+      table.insert(argnodes, e.pos)
+      if e.pos > maxpos then
+        maxpos = e.pos
+      end
+    end
+  end
+
+  local nodes = {}
+  table.insert(nodes, ls.d(maxpos+1, autoinsert, argnodes, {user_args = {top}}))
+  for _, e in ipairs(inner) do table.insert(nodes, e) end
+  table.insert(nodes, ls.d(maxpos+2, autoinsert, argnodes, {user_args = {bot}}))
+  return nodes
+end
+
+-- comment: wraps the content in &commentstring (or a blockcomment)
+local comment = function(wrapped, blockcomment)
+  -- commentstring helper
+  local get_cstring = function()
+    local cs = require('Comment.ft').calculate {
+      ctype = blockcomment and 2 or 1,
+      range = require('Comment.utils').get_region()
+    }
+    local tbl = vim.split(cs or '', '%s', {plain = true, trimempty = true})
+    return (#tbl == 0) and {'', ''}
+      or ((#tbl == 1) and {tbl[1] .. ' ', ''}
+        or {tbl[1], tbl[2]})
+  end
+
+  if type(wrapped) ~= "table" then
+    wrapped = {wrapped}
+  end
+
+  local nodes = {}
+  table.insert(nodes, ls.f(function() return get_cstring()[1] end))
+  for _, n in ipairs(wrapped) do table.insert(nodes, n) end
+  table.insert(nodes, ls.f(function() return get_cstring()[2] end))
+  return nodes
+end
+
+-- exec: inserts the output of command
+local exec = function(command)
+  return ls.f(function(_, _, ...)
+    local results, code = require('plenary.job'):new({
+      command = vim.env.SHELL,
+      args = {'-c', ...},
+      enable_recording = true,
+    }):sync()
+    if not code == 0 or #results == 0 then
+      error(string.format('exec "%s" failed', ...))
+    end
+    return results
+  end, {}, {user_args = {command}})
+end
+
+ls.setup {
+  ext_opts = {
+    [types.snippet] = {
+      active = { virt_text = {{ '<-- luasnip', 'LuasnipIndicator' }}, virt_text_pos = 'right_align' }
+    },
+    [types.insertNode] = {
+      unvisited = { hl_group = 'LuasnipIndicator' }
+    },
+    [types.choiceNode] = {
+      active = {
+        virt_text = {{'<-- choice node', 'LuasnipIndicator'}},
+      }
+    }
+  },
+
+  snip_env = vim.tbl_extend('keep', ls.session.config.snip_env, {
+    autowrap = autowrap,
+    exec = exec,
+    comment = comment,
+
+    user_email = {
+      exec([[git config --get user.name]]),
+      ls.t(' <'),
+      ls.c(1, {
+        exec([[git config --get user.email]]),
+        ls.t([[robert.gunzler@postman.com]]),
+        ls.t([[robert@gnzler.io]]),
+      }),
+      ls.t('>'),
+    },
+  })
+}
+
+-- snipmate snippets
+require('luasnip.loaders.from_snipmate').lazy_load {paths = './after/snippets'}
+-- lua snippets
+require("luasnip.loaders.from_lua").lazy_load {paths = './snippets'}
+
+vim.api.nvim_create_user_command('LuaSnipUnlinkAll',
+  function()
+    while #package.loaded.luasnip.session.current_nodes > 0 do
+      package.loaded.luasnip.unlink_current()
+    end
+  end,
+  { desc = 'unlink all active snippets' })
diff --git a/.vim/lua/internal/spell.lua b/.vim/lua/internal/spell.lua
new file mode 100644
index 0000000..57cbad2
--- /dev/null
+++ b/.vim/lua/internal/spell.lua
@@ -0,0 +1,9 @@
+
+vim.opt_local.spell = true
+vim.opt.spellfile = vim.fn['spellfile#WritableSpellDir']() .. '/spellfile.utf-8.add'
+vim.opt.spellcapcheck = ''
+vim.opt.spelloptions = {'camel'}
+vim.opt.spellsuggest = 'double'
+
+vim.keymap.set('n', '<C-l>', require('telescope.builtin').spell_suggest)
+vim.keymap.set('n', '<C-l>', [[<c-g>u<esc>[s1z=`]a<c-g>u]])
diff --git a/.vim/lua/internal/statusline.lua b/.vim/lua/internal/statusline.lua
new file mode 100644
index 0000000..97f5031
--- /dev/null
+++ b/.vim/lua/internal/statusline.lua
@@ -0,0 +1,255 @@
+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 .. statusline_append(statusline_lightbulb(),
+    'StatusLineLightbulb', hi)
+
+  sl = sl .. statusline_append(statusline_ts(),
+    'StatusLineTreesitter', hi)
+
+  sl = sl .. statusline_append(statusline_lsp_diagnostics(bufnr, hi),
+    'StatusLineDiagnostics', hi)
+
+  sl = sl .. statusline_append(statusline_lspstatus(),
+    'StatusLineLsp', hi)
+
+  sl = sl .. statusline_append(statusline_dap(),
+    'StatusLineDap', hi)
+
+  sl = sl .. [[ %= ]] -- spacer
+
+  sl = sl .. statusline_append(statusline_notify(hi),
+    'StatusLineNotify', hi)
+
+  sl = sl .. statusline_append(statusline_spinner(bufnr),
+    'StatusLineJobs', hi)
+
+  sl = sl .. statusline_append([[ %l:%v --%p%%-- %y]], hi, hi)
+
+  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_by_id(vim.api.nvim_get_hl_id_by_name(outer_name), true)
+  local inner = vim.api.nvim_get_hl_by_id(vim.api.nvim_get_hl_id_by_name(inner_name), true)
+  local hi = 'auto' .. outer_name .. inner_name
+  if pcall(vim.api.nvim_get_hl_by_name, hi, true)  then return hi end
+  local gui
+  gui = inner.bold and 'bold'
+  gui = inner.italic and 'italic'
+  gui = inner.underline and 'underline'
+  vim.api.nvim_set_hl(0, hi, {bg = outer.background, fg = inner.foreground, gui = gui})
+  return hi
+end
+
+function _G.statusline_lsp_diagnostics(bufnr, default_hi)
+  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 = vim.fn.sign_getdefined('DiagnosticSignError')[1].text .. #error_num
+    s = s .. statusline_append(errs, statusline_combine_hi('StatusLineDiagnostics', 'DiagnosticError'), default_hi)
+  end
+  if #warn_num > 0 then
+    local warns = vim.fn.sign_getdefined('DiagnosticSignWarn')[1].text .. #warn_num
+    s = s .. statusline_append(warns, statusline_combine_hi('StatusLineDiagnostics', 'DiagnosticWarn'), default_hi)
+  end
+  return vim.trim(s)
+end
+function _G.statusline_ts()
+  local navic = package.loaded['nvim-navic']
+  if not navic then return '' end
+  local data = navic.get_data()
+  if not data then return '' end
+  local s = {}
+  for i = 1, #data do
+    table.insert(s, string.format('%%#CmpItemKind%s#%s%%* %%#StatusLine#%s%%*', data[i].type, data[i].icon, data[i].name))
+  end
+  return table.concat(s, ' > ')
+end
+function _G.statusline_lspstatus()
+  local lst = package.loaded['lsp-status']
+  if not lst then return '' end
+  return vim.trim(lst.status())
+end
+function _G.statusline_lightbulb()
+  local lb = package.loaded['nvim-lightbulb']
+  if not lb then return '' end
+  return lb.get_status_text()
+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
+
+-- notifications cache
+_G.statusline_notifications = {}
+_G.statusline_notifications_archive = {}
+
+function _G.statusline_notify(default_hi)
+  if #_G.statusline_notifications == 0 then return '' end
+  local n = _G.statusline_notifications[1]
+  local timeout = (n.options and n.options.timeout) or 2000
+  -- pop message after <timeout> and move to archive
+  vim.defer_fn(function()
+    local n = table.remove(_G.statusline_notifications, 1)
+    table.insert(_G.statusline_notifications_archive, n)
+    -- make sure archive is never longer than 20 notifications
+    if #_G.statusline_notifications_archive > 20 then
+      table.remove(_G.statusline_notifications_archive, 1)
+    end
+  end, timeout)
+
+  local s = ''
+  if n.hi then
+    s = s .. statusline_append(n.lvl_string .. ' ', statusline_combine_hi('StatusLineNotify', n.hi), default_hi, { append_space = false })
+  end
+  s = s .. statusline_append(n.message, 'StatusLineNotify', default_hi)
+  return vim.trim(s)
+end
+
+-- out vim.notify implementation
+local notify = function(m, l, o)
+  local lvl_string = l
+  local hi = nil
+  if l and type(l) == 'number' then
+    if l == vim.log.levels.TRACE then
+      hi = 'deEmph'
+    elseif l == vim.log.levels.DEBUG then
+      hi = 'DiagnosticHint'
+    elseif l == vim.log.levels.INFO then
+      hi = 'DiagnosticInfo'
+    elseif l == vim.log.levels.WARN then
+      hi = 'DiagnosticWarn'
+    elseif l == vim.log.levels.ERROR then
+      hi = 'DiagnosticError'
+    end
+    -- get level string
+    lvl_string = vim.lsp.log_levels[l]
+  end
+  local display = m
+  if lvl_string then display = lvl_string .. ' ' .. display end
+  table.insert(_G.statusline_notifications, {
+    message = m,
+    level = l,
+    options = o,
+    lvl_string = lvl_string,
+    hi = hi,
+    display = display,
+  })
+
+  if _G.statusline_enable_notifysend then
+    local args = {}
+    if lvl_string then
+      if lvl_string == 'ERROR' then
+        table.insert(args, '--category=error')
+      end
+      if lvl_string == 'WARN' then
+        table.insert(args, '--category=warning')
+      end
+    end
+    table.insert(args, 'nvim')
+    table.insert(args, display)
+    require('internal.job').jobstart { command = 'notify-send', args = args }
+  end
+end
+
+-- overwrite vim.notify
+vim.notify = function( m, l, o)
+  if vim.in_fast_event() then
+    vim.schedule(function()
+      notify(m, l, o)
+    end)
+  else
+    return notify(m, l, o)
+  end
+end
+
+vim.keymap.set('n', '<leader>n', function()
+  local pickers = require('telescope.pickers')
+  local finders = require('telescope.finders')
+  local conf = require('telescope.config').values
+  local actions = require('telescope.actions')
+  -- local action_state = require('telescope.actions.state')
+  -- local previewers = require('telescope.previewers')
+
+  local opts = {}
+  pickers.new(opts, {
+    prompt_title = "Notifications",
+    finder = finders.new_dynamic {
+      fn = function()
+        return _G.statusline_notifications_archive
+      end,
+      entry_maker = function(n)
+        return {
+          value = n,
+          display = n.display,
+          ordinal = n.message,
+        }
+      end,
+    },
+    sorter = conf.generic_sorter(opts),
+    attach_mappings = function(prompt_bufnr, _)
+      actions.select_default:replace(function()
+        actions.close(prompt_bufnr)
+        -- noop
+        -- local sel = action_state.get_selected_entry()
+        -- vim.notify(sel.value.message, sel.value.level, sel.value.options)
+      end)
+      return true
+    end,
+    -- previewer = previewers.new_buffer_previewer {
+    --   define_preview = function(self, entry)
+    --     vim.api.nvim_buf_set_lines(self.state.bufnr, 0, -1, false, {entry.value.display})
+    --   end,
+    -- },
+  }):find()
+end, {desc='notifications'})
+
+-- vim.opt.statusline = '%!luaeval("statusline()")'
+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()%}'