Selaa lähdekoodia

nixpkgs/neovim: further reorganize configuration

plugins.lua was split into multiple files. I've gone with putting
mappings closer to the plugins they refer to since some plugins define
mappings in `setup()` and this keeps them closer to each other
Thomas Dy 3 viikkoa sitten
vanhempi
commit
803dd55955

+ 1 - 0
.config/nixpkgs/neovim/config/ftplugin/markdown.lua

@@ -0,0 +1 @@
+vim.bo.textwidth = 80

+ 0 - 92
.config/nixpkgs/neovim/config/lua/user/autocmd.lua

@@ -1,92 +0,0 @@
--- apply default terminal settings
-vim.api.nvim_create_autocmd('TermOpen', {
-  callback = function()
-    vim.bo.scrollback = 10000
-    vim.opt_local.number = false
-    vim.opt_local.relativenumber = false
-    vim.b.miniindentscope_disable = true
-  end
-})
-
--- preserve window structure when exiting terminal via C-d
-vim.api.nvim_create_autocmd('TermClose', {
-  callback = function(opts)
-    -- don't trigger when force deleting
-    if vim.api.nvim_buf_get_option(opts.buf, 'modified') then
-      return
-    end
-    MiniBufremove.delete(opts.buf)
-  end,
-  -- needed so statusline properly updates
-  nested = true,
-})
-
--- automatically enter/leave terminal mode
-vim.api.nvim_create_autocmd('TermOpen', { command = 'startinsert' })
-vim.api.nvim_create_autocmd({ 'WinEnter', 'BufWinEnter' }, {
-  pattern = 'term://*',
-  command = 'startinsert',
-})
-vim.api.nvim_create_autocmd('BufLeave', {
-  pattern = 'term://*',
-  command = 'stopinsert',
-})
-
--- rename
-vim.api.nvim_create_autocmd('User', {
-  pattern = 'MiniFilesActionRename',
-  callback = function(opts)
-    local params = {
-      files = { {
-        oldUri = vim.uri_from_fname(opts.data.from),
-        newUri = vim.uri_from_fname(opts.data.to),
-      } }
-    }
-    local bufnr = vim.fn.bufadd(opts.data.to)
-
-    local clients = vim.lsp.get_clients({ bufnr = bufnr })
-    for _, client in ipairs(clients) do
-      if client:supports_method('workspace/willRenameFiles') then
-        local resp = client:request_sync('workspace/willRenameFiles', params, 5000, bufnr)
-        if resp and resp.result ~= nil then
-          vim.lsp.util.apply_workspace_edit(resp.result, client.offset_encoding)
-        end
-      end
-    end
-
-    for _, client in ipairs(clients) do
-      if client:supports_method('workspace/didRenameFiles') then
-        client:notify('workspace/didRenameFiles', params)
-      end
-    end
-  end,
-  nested = true,
-})
-
--- formatting
-vim.api.nvim_create_autocmd('BufWritePre', {
-  callback = function(opts)
-    if vim.bo.filetype == 'diff' then
-      return
-    end
-
-    if not vim.g.no_lsp_format then
-      -- check if can LSP format
-      local clients = vim.lsp.get_clients({ bufnr = opts.buf, method = 'textDocument/formatting' })
-      if #clients > 0 then
-        vim.lsp.buf.format({ bufnr = opts.buf })
-      end
-    end
-
-    -- otherwise strip trailing whitespace
-    vim.cmd('%s/\\s\\+$//e')
-  end,
-})
-
--- filetype specific options
-vim.api.nvim_create_autocmd('FileType', {
-  pattern = 'markdown',
-  callback = function()
-    vim.bo.textwidth = 80
-  end,
-})

+ 1 - 2
.config/nixpkgs/neovim/config/lua/user/init.lua

@@ -1,7 +1,6 @@
 require('user.settings')
 require('user.plugins')
-require('user.mappings')
-require('user.autocmd')
+require('user.terminal')
 require('user.lsp')
 require('user.commands')
 require('user.theme')

+ 102 - 40
.config/nixpkgs/neovim/config/lua/user/lsp.lua

@@ -1,3 +1,5 @@
+local util = require('user.util')
+
 vim.diagnostic.config({
   -- only show virtual text for WARN and higher
   virtual_text = { severity = { min = vim.diagnostic.severity.WARN } },
@@ -9,18 +11,6 @@ vim.lsp.config('*', {
   }
 })
 
-local function read_helix_config()
-  local tinytoml = require('tinytoml')
-  local data = vim.secure.read('.helix/languages.toml')
-  if data ~= nil then
-    local status, data = pcall(tinytoml.parse, data, { load_from_string = true })
-    if status then
-      return data
-    end
-  end
-  return {}
-end
-
 vim.lsp.enable('bashls');
 vim.lsp.enable('eslint');
 
@@ -40,8 +30,13 @@ else
     providePrefixAndSuffixTextForRename = false,
   }
 
-  local helix_preferences = vim.tbl_get(read_helix_config(), 'language-server', 'typescript-language-server', 'config',
-    'preferences')
+  local helix_preferences = vim.tbl_get(
+    util.read_helix_config(),
+    'language-server',
+    'typescript-language-server',
+    'config',
+    'preferences'
+  )
   if helix_preferences ~= nil then
     preferences = vim.tbl_deep_extend('force', preferences, helix_preferences)
   end
@@ -86,35 +81,27 @@ else
   vim.lsp.enable('ts_ls')
 end
 
-if vim.fn.executable('gopls') == 1 then
-  vim.lsp.enable('gopls')
-end
+vim.lsp.enable('gopls')
 
-if vim.fn.executable('ruby-lsp') == 1 then
-  vim.lsp.config('ruby_lsp', {
-    init_options = {
-      enabledFeatures = {
-        formatting = false,
-      },
-    }
-  })
-  vim.lsp.enable('ruby_lsp')
-end
+vim.lsp.config('ruby_lsp', {
+  init_options = {
+    enabledFeatures = {
+      formatting = false,
+    },
+  }
+})
+vim.lsp.enable('ruby_lsp')
 
-if vim.fn.executable('nil') == 1 then
-  vim.lsp.enable('nil_ls')
-end
+vim.lsp.enable('nil_ls')
 
-if vim.fn.executable('jdtls') == 1 then
-  vim.lsp.config('jdtls', {
-    handlers = {
-      ['$/progress'] = function()
-        -- this is quite noisy so just disable it
-      end
-    },
-  })
-  vim.lsp.enable('jdtls')
-end
+vim.lsp.config('jdtls', {
+  handlers = {
+    ['$/progress'] = function()
+      -- this is quite noisy so just disable it
+    end
+  },
+})
+vim.lsp.enable('jdtls')
 
 vim.lsp.enable('lua_ls')
 
@@ -125,3 +112,78 @@ vim.lsp.config('elvish', {
   settings = {},
 })
 vim.lsp.enable('elvish')
+
+-- diagnostics
+vim.keymap.set('n', '<space>e', function() vim.diagnostic.open_float() end)
+vim.keymap.set('n', '[d',
+  function() vim.diagnostic.jump({ count = -1, float = true, severity = vim.diagnostic.severity.ERROR }) end)
+vim.keymap.set('n', ']d',
+  function() vim.diagnostic.jump({ count = 1, float = true, severity = vim.diagnostic.severity.ERROR }) end)
+vim.keymap.set('n', '[D', function() vim.diagnostic.jump({ count = -1, float = true }) end)
+vim.keymap.set('n', ']D', function() vim.diagnostic.jump({ count = 1, float = true }) end)
+vim.keymap.set('n', '<space>qe', function() vim.diagnostic.setqflist({ severity = vim.diagnostic.severity.ERROR }) end)
+vim.keymap.set('n', '<space>qw',
+  function() vim.diagnostic.setqflist({ severity = { min = vim.diagnostic.severity.WARN } }) end)
+vim.keymap.set('n', '<space>qd', function() vim.diagnostic.setqflist({}) end)
+vim.keymap.set('n', '<space>qc', '<cmd>cclose<CR>')
+
+-- LSP-specific
+vim.api.nvim_create_autocmd('LspAttach', {
+  callback = function(args)
+    local client = vim.lsp.get_client_by_id(args.data.client_id)
+
+    if client.name == 'dprint' or client.name == 'eslint' then
+      -- mappings should have been attached by typescript and re-attaching can
+      -- overwrite the typescript specific overrides
+      return
+    end
+
+    local opts = { buffer = args.buf }
+
+    vim.keymap.set('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', opts)
+    vim.keymap.set('n', 'gd', '<cmd>Telescope lsp_definitions<CR>', opts)
+    vim.keymap.set('n', '<space>D', '<cmd>Telescope lsp_type_definitions<CR>', opts)
+    -- as much as possible we try to use the default bindings, replacing them
+    -- with Telescope if appropriate
+    vim.keymap.set('n', 'gri', '<cmd>Telescope lsp_implementations<CR>', opts)
+    vim.keymap.set('n', 'grr', '<cmd>Telescope lsp_references<CR>', opts)
+    vim.keymap.set('n', 'gO', '<cmd>Telescope lsp_document_symbols<CR>', opts)
+
+    if client.name == 'ts_ls' then
+      -- exclude import statements from reference search (may have false positives)
+      vim.keymap.set('n', 'grr', '<cmd>Telescope lsp_references default_text=!import\\ <CR>', opts)
+      vim.keymap.set('n', 'grA', '<cmd>LspTypescriptSourceAction<CR>', opts)
+    end
+  end,
+})
+
+-- handle file renames
+vim.api.nvim_create_autocmd('User', {
+  pattern = 'MiniFilesActionRename',
+  callback = function(opts)
+    local params = {
+      files = { {
+        oldUri = vim.uri_from_fname(opts.data.from),
+        newUri = vim.uri_from_fname(opts.data.to),
+      } }
+    }
+    local bufnr = vim.fn.bufadd(opts.data.to)
+
+    local clients = vim.lsp.get_clients({ bufnr = bufnr })
+    for _, client in ipairs(clients) do
+      if client:supports_method('workspace/willRenameFiles') then
+        local resp = client:request_sync('workspace/willRenameFiles', params, 5000, bufnr)
+        if resp and resp.result ~= nil then
+          vim.lsp.util.apply_workspace_edit(resp.result, client.offset_encoding)
+        end
+      end
+    end
+
+    for _, client in ipairs(clients) do
+      if client:supports_method('workspace/didRenameFiles') then
+        client:notify('workspace/didRenameFiles', params)
+      end
+    end
+  end,
+  nested = true,
+})

+ 0 - 123
.config/nixpkgs/neovim/config/lua/user/mappings.lua

@@ -1,123 +0,0 @@
-local opts = { silent = true }
-
--- ; as :
-vim.keymap.set({ 'n', 'v' }, ';', ':')
-
--- leave insert mode via jj
-vim.keymap.set('i', 'jj', '<ESC>', opts)
-
--- j/k with wraps
-vim.keymap.set({ 'n', 'v' }, 'j', 'gj', opts)
-vim.keymap.set({ 'n', 'v' }, 'k', 'gk', opts)
-
--- treesitter navigation
-vim.keymap.set({ 'n', 'v' }, '<C-k>', '<cmd>Treewalker Up<cr>', opts)
-vim.keymap.set({ 'n', 'v' }, '<C-j>', '<cmd>Treewalker Down<cr>', opts)
-vim.keymap.set({ 'n', 'v' }, '<C-h>', '<cmd>Treewalker Left<cr>', opts)
-vim.keymap.set({ 'n', 'v' }, '<C-l>', '<cmd>Treewalker Right<cr>', opts)
-
--- leave insert mode with <ESC><ESC>
-vim.keymap.set('t', '<ESC><ESC>', '<C-\\><C-N>', opts)
-vim.keymap.set('t', '<C-[><C-[>', '<C-\\><C-N>', opts)
-
--- select pasted text
-vim.keymap.set('n', 'gp', '`[v`]', opts)
-
--- pickers
-vim.keymap.set('n', '<C-P>', '<cmd>lua project_files()<CR>', opts)
-vim.keymap.set('n', '<C-O>', '<cmd>Telescope buffers<CR>', opts)
-vim.keymap.set('n', '<Leader>fg', '<cmd>lua commit_files()<CR>', opts)
-vim.keymap.set('n', '<Leader>ff', '<cmd>Telescope live_grep<CR>', opts)
-vim.keymap.set('n', '<Leader>fr', '<cmd>Telescope resume<CR>', opts)
-vim.keymap.set('n', '<Leader>fp', '<cmd>Telescope pickers<CR>', opts)
-
--- directory tree
-vim.keymap.set('n', '<Leader>ft', function() MiniFiles.open(MiniFiles.get_latest_path()) end, opts)
-vim.keymap.set('n', '<Leader>fc', function() MiniFiles.open(vim.api.nvim_buf_get_name(0)) end, opts)
-
--- jumplist navigation
-vim.keymap.set('n', '[f', '<C-O>', opts)
-vim.keymap.set('n', ']f', '<C-I>', opts)
-
--- conflicts navigation
-vim.keymap.set('n', '[x', '?^[<lt>=>]\\{7}<CR>', opts)
-vim.keymap.set('n', ']x', '/^[<lt>=>]\\{7}<CR>', opts)
-
--- window navigation
-vim.keymap.set('n', '[w', '<C-w>W', opts)
-vim.keymap.set('n', ']w', '<C-w>w', opts)
-
--- cursor display
-vim.keymap.set('n', '<Leader>c', '<cmd>set cursorline! cursorcolumn!<CR>', opts)
-
--- notification history
-vim.keymap.set('n', '<Leader>n', require('mini.notify').show_history, opts)
-
--- git
--- open status in current split without special flag (so navigating away closes it)
--- https://github.com/tpope/vim-fugitive/issues/1296
-vim.keymap.set('n', '<Leader>gs', '<cmd>Gedit :<CR>', opts)
-vim.keymap.set('n', '<Leader>gb', '<cmd>Telescope git_branches<CR>', opts)
-vim.keymap.set('n', '<Leader>gd', function() MiniDiff.toggle_overlay() end, opts)
-
--- undotree
-vim.keymap.set('n', '<Leader>ut', '<cmd>UndotreeToggle<CR>', opts)
-
--- opening terminals
-vim.keymap.set('n', '<Leader>tv', '<cmd>vertical term $SHELL<CR>', opts)
-vim.keymap.set('n', '<Leader>to', '<cmd>term $SHELL<CR>', opts)
-
--- delete buffer
-vim.keymap.set('n', '<Leader>q', '<cmd>lua MiniBufremove.delete()<CR>', opts)
-vim.keymap.set('n', '<Leader>Q', '<cmd>lua MiniBufremove.delete(0, true)<CR>', opts)
-
--- split/join
-vim.keymap.set('n', '<Leader>s', '<cmd>TSJSplit<CR>', opts)
-vim.keymap.set('n', '<Leader>j', '<cmd>TSJJoin<CR>', opts)
-
--- add ,/; to end of line
-vim.keymap.set('i', '<C-,>', function() toggle_end_char(',') end, opts)
-vim.keymap.set('i', '<C-;>', function() toggle_end_char(';') end, opts)
-
--- diagnostics
-vim.keymap.set('n', '<space>e', function() vim.diagnostic.open_float() end, opts)
-vim.keymap.set('n', '[d',
-  function() vim.diagnostic.jump({ count = -1, float = true, severity = vim.diagnostic.severity.ERROR }) end, opts)
-vim.keymap.set('n', ']d',
-  function() vim.diagnostic.jump({ count = 1, float = true, severity = vim.diagnostic.severity.ERROR }) end, opts)
-vim.keymap.set('n', '[D', function() vim.diagnostic.jump({ count = -1, float = true }) end, opts)
-vim.keymap.set('n', ']D', function() vim.diagnostic.jump({ count = 1, float = true }) end, opts)
-vim.keymap.set('n', '<space>qe', function() vim.diagnostic.setqflist({ severity = vim.diagnostic.severity.ERROR }) end,
-  opts)
-vim.keymap.set('n', '<space>qw',
-  function() vim.diagnostic.setqflist({ severity = { min = vim.diagnostic.severity.WARN } }) end, opts)
-vim.keymap.set('n', '<space>qd', function() vim.diagnostic.setqflist({}) end, opts)
-vim.keymap.set('n', '<space>qc', '<cmd>cclose<CR>', opts)
-
--- LSP-specific
-vim.api.nvim_create_autocmd('LspAttach', {
-  callback = function(args)
-    local client = vim.lsp.get_client_by_id(args.data.client_id)
-
-    if client.name == 'dprint' or client.name == 'eslint' then
-      -- mappings should have been attached by typescript and re-attaching can
-      -- overwrite the typescript specific overrides
-      return
-    end
-
-    local opts = { silent = true, buffer = args.buf }
-
-    vim.keymap.set('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', opts)
-    vim.keymap.set('n', 'gd', '<cmd>Telescope lsp_definitions<CR>', opts)
-    vim.keymap.set('n', 'gri', '<cmd>Telescope lsp_implementations<CR>', opts)
-    vim.keymap.set('n', '<space>D', '<cmd>Telescope lsp_type_definitions<CR>', opts)
-    vim.keymap.set('n', 'grr', '<cmd>Telescope lsp_references<CR>', opts)
-    vim.keymap.set('n', 'gO', '<cmd>Telescope lsp_document_symbols<CR>', opts)
-
-    if client.name == 'ts_ls' then
-      -- exclude import statements from reference search (may have false positives)
-      vim.keymap.set('n', 'grr', '<cmd>Telescope lsp_references default_text=!import\\ <CR>', opts)
-      vim.keymap.set('n', 'grA', '<cmd>LspTypescriptSourceAction<CR>', opts)
-    end
-  end,
-})

+ 0 - 427
.config/nixpkgs/neovim/config/lua/user/plugins.lua

@@ -1,427 +0,0 @@
--- add extra filetypes for plenary
-require('plenary.filetype').add_table({
-  extension = {
-    ['elv'] = [[elvish]]
-  }
-})
-
--- add extra builtin filetypes
-vim.filetype.add({
-  pattern = {
-    ['.*%.ts$'] = 'typescript'
-  },
-})
-
--- helpers
-local function resolve_git_path(buf_id)
-  local bufname = vim.api.nvim_buf_get_name(buf_id)
-  if not vim.startswith(bufname, 'fugitive://') then
-    return false
-  end
-
-  local parsed = vim.fn.FugitiveParse(bufname)
-  local resolved_path = parsed[1]
-  local repo = parsed[2]
-
-  if resolved_path == '' then
-    return false
-  end
-
-  local parts = vim.split(resolved_path, ':')
-  local commit = parts[1]
-  local path = parts[2]
-
-  return {
-    repo = repo,
-    commit = commit,
-    path = path,
-  }
-end
-
--- file/buffer/etc picker
-local telescope_actions = require('telescope.actions')
-local action_state = require('telescope.actions.state')
-local actions = {}
-
-actions.git_show_commit = function(prompt_bufnr)
-  local selection = action_state.get_selected_entry()
-  local path = vim.fn.FugitiveFind(selection.value)
-  telescope_actions.close(prompt_bufnr)
-  vim.cmd.edit(path)
-end
-
-local commit_mappings = {
-  i = {
-    ['<CR>'] = actions.git_show_commit,
-    ['<C-r>c'] = telescope_actions.git_checkout,
-  }
-}
-
-require('telescope').setup({
-  defaults = {
-    mappings = {
-      i = {
-        ['jj'] = 'close',
-      },
-    },
-    layout_config = {
-      prompt_position = 'top',
-    },
-    sorting_strategy = 'ascending',
-    -- use filename as preview window title
-    dynamic_preview_title = true,
-
-    path_display = {
-      -- shorten directory names of everything but the last 3 parts
-      -- foo/bar/baz/file.txt -> f/boo/bar/file.txt
-      shorten = { len = 1, exclude = { -3, -2, -1 } },
-
-      -- truncate the beginning of the file name if wider than the window
-      truncate = true,
-    },
-
-    preview = {
-      -- don't preview files larger than 1MB
-      filesize_limit = 1,
-      timeout = 500,
-    },
-
-    vimgrep_arguments = {
-      -- defaults
-      'rg',
-      '--color=never',
-      '--no-heading',
-      '--with-filename',
-      '--line-number',
-      '--column',
-      '--smart-case',
-      -- search "hidden" files except git folder
-      '--hidden',
-      '--iglob=!.git'
-    },
-
-    -- ignore things we're likely not to edit
-    file_ignore_patterns = {
-      '%.zip$',
-      '%.yarn/releases/',
-      '%.yarn/plugins/'
-    },
-
-    -- picker history
-    cache_picker = {
-      num_pickers = 10,
-    },
-  },
-  pickers = {
-    buffers = {
-      sort_lastused = true,
-      sort_mru = true,
-      mappings = {
-        i = {
-          ['<C-k>'] = 'delete_buffer'
-        },
-      },
-    },
-    find_files = {
-      find_command = { 'fd', '--type', 'f', '--strip-cwd-prefix' }
-    },
-    git_commits = {
-      mappings = commit_mappings,
-    },
-    git_bcommits = {
-      mappings = commit_mappings,
-    },
-  },
-})
-
--- use native sorter for better performance
-require('telescope').load_extension('fzf')
-
-local telescope_builtin = require('telescope.builtin')
-local telescope_pickers = require('telescope.pickers')
-local telescope_finders = require('telescope.finders')
-local telescope_previewers = require('telescope.previewers')
-local telescope_putils = require('telescope.previewers.utils')
-local telescope_conf = require('telescope.config').values
-
--- custom picker to fallback to files if no git
-_G.project_files = function()
-  local ok = pcall(telescope_builtin.git_files, { show_untracked = true })
-  if not ok then telescope_builtin.find_files({}) end
-end
-
--- custom picker for files within a commit
-_G.commit_files = function(opts)
-  local resolved = resolve_git_path(0)
-  if not resolved then
-    vim.print('current file is not a fugitive path')
-    return
-  end
-
-  opts = opts or {}
-  telescope_pickers.new(opts, {
-    prompt_title = resolved.commit,
-    finder = telescope_finders.new_oneshot_job({ 'git', 'ls-tree', '--name-only', '-r', resolved.commit }, {
-      entry_maker = function(entry)
-        local path = string.format('fugitive://%s//%s/%s', resolved.repo, resolved.commit, entry)
-        return {
-          path = path,
-          value = entry,
-          display = entry,
-          ordinal = entry,
-        }
-      end,
-    }),
-    sorter = telescope_conf.file_sorter(opts),
-
-    -- the builtin previewer has fancy async loading which doesn't work for
-    -- fugitive paths so we have to define our own
-    previewer = telescope_previewers.new_buffer_previewer({
-      title = function(self)
-        return 'Commit Files'
-      end,
-      dyn_title = function(self, entry)
-        return entry.value
-      end,
-      define_preview = function(self, entry, status)
-        -- the builtin previewer does more things like using mime type
-        -- fallbacks as well as binary file detection which ours doesn't do
-        local ft = telescope_putils.filetype_detect(entry.value)
-
-        vim.api.nvim_buf_call(self.state.bufnr, function()
-          vim.cmd('Gread ' .. entry.path)
-          telescope_putils.highlighter(self.state.bufnr, ft, opts)
-        end)
-      end,
-    }),
-  }):find()
-end
-
--- shows added/removed/changed lines
-local MiniDiff = require('mini.diff')
-MiniDiff.setup({
-  view = {
-    style = 'sign',
-    signs = {
-      delete = '_',
-    },
-  },
-  source = {
-    MiniDiff.gen_source.git(),
-    -- handle fugitive paths
-    {
-      name = 'fugitive',
-      attach = function(buf_id)
-        local resolved = resolve_git_path(buf_id)
-        if not resolved or resolved.path == '' then
-          return false
-        end
-
-        local source = vim.fn.FugitiveFind(string.format('%s~1:%s', resolved.commit, resolved.path))
-        local text = vim.fn['fugitive#readfile'](source)
-        MiniDiff.set_ref_text(buf_id, text)
-      end
-    }
-  }
-})
-
-local function section_git(args)
-  if MiniStatusline.is_truncated(args.trunc_width) then return '' end
-  return vim.fn.FugitiveHead()
-end
-
-require('mini.statusline').setup({
-  content = {
-    -- copy-pasted from default, we just want to remove the icon
-    active = function()
-      local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 })
-      local git           = section_git({ trunc_width = 75 })
-      local diagnostics   = MiniStatusline.section_diagnostics({ trunc_width = 75, icon = '' })
-      local filename      = MiniStatusline.section_filename({ trunc_width = 140 })
-      local fileinfo      = MiniStatusline.section_fileinfo({ trunc_width = 120 })
-      local location      = MiniStatusline.section_location({ trunc_width = 75 })
-
-      return MiniStatusline.combine_groups({
-        { hl = mode_hl,                 strings = { mode } },
-        { hl = 'MiniStatuslineDevinfo', strings = { git, diagnostics } },
-        '%<', -- Mark general truncate point
-        { hl = 'MiniStatuslineFilename', strings = { filename } },
-        '%=', -- End left alignment
-        { hl = 'MiniStatuslineFileinfo', strings = { fileinfo } },
-        { hl = mode_hl,                  strings = { location } },
-      })
-    end
-  },
-})
-
--- delete buffer while preserving layout
-require('mini.bufremove').setup()
-
--- shows a line indicating the current indentation scope
-require('mini.indentscope').setup()
-
--- comment actions
-require('mini.comment').setup()
-
--- surround actions
-require('mini.surround').setup({
-  custom_surroundings = {
-    -- js template string
-    ['$'] = {
-      output = {
-        left = '`${',
-        right = '}`',
-      },
-    },
-  },
-})
-
-local spec_treesitter = require('mini.ai').gen_spec.treesitter
-
-require('mini.ai').setup({
-  -- only consider the current location
-  search_method = 'cover',
-  custom_textobjects = {
-    ['.'] = spec_treesitter({
-      a = '@call.outer',
-      i = '@call.inner',
-    }),
-    [','] = spec_treesitter({
-      a = '@parameter.outer',
-      i = '@parameter.inner',
-    }),
-  },
-});
-
--- align actions
-require('mini.align').setup()
-
--- repeatable f/t
-require('mini.jump').setup({
-  mappings = {
-    repeat_jump = '',
-  },
-  delay = {
-    highlight = 10000000,
-  },
-})
-
--- autopair brackets
-require('mini.pairs').setup({
-  mappings = {
-    -- default config includes (, [ and {
-
-    -- autopair <> if preceded by a character, otherwise it might be a regular
-    -- comparison operation
-    ['<'] = { action = 'open', pair = '<>', neigh_pattern = '%w.' },
-    ['>'] = { action = 'close', pair = '<>' },
-  },
-})
-
--- notifications
-require('mini.notify').setup()
-vim.notify = require('mini.notify').make_notify()
-
--- file explorer
-require('mini.files').setup({
-  content = {
-    -- remove icons
-    prefix = function() end,
-  }
-})
-
--- Use Treesitter for syntax highlighting
-require('nvim-treesitter.configs').setup({
-  highlight = {
-    enable = true,
-  },
-  indent = {
-    enable = true,
-  },
-  incremental_selection = {
-    enable = true,
-    keymaps = {
-      init_selection = ']t',
-      node_incremental = ']t',
-      node_decremental = '[t',
-    },
-  },
-  textobjects = {
-    swap = {
-      enable = true,
-      swap_next = {
-        ['>,'] = '@parameter.inner',
-      },
-      swap_previous = {
-        ['<,'] = '@parameter.inner',
-      },
-    },
-  },
-})
-
-local tsj_utils = require('treesj.langs.utils')
-
--- Treesitter-aware split/join
-require('treesj').setup({
-  use_default_keymaps = false,
-})
-
--- Treesitter context
-require('treesitter-context').setup({
-  enable = true,
-  multiline_threshold = 5,
-})
-
--- Treesitter navigation
-require('treewalker').setup()
-
--- completion
-require('blink.cmp').setup({
-  cmdline = {
-    enabled = false,
-  },
-  completion = {
-    accept = {
-      auto_brackets = {
-        enabled = false,
-      },
-    },
-    documentation = {
-      auto_show = true,
-      auto_show_delay_ms = 500,
-    },
-    trigger = {
-      prefetch_on_insert = false,
-      show_on_keyword = false,
-      show_on_trigger_character = false,
-    },
-    menu = {
-      draw = {
-        columns = {
-          { 'label', 'label_description', gap = 1 },
-          { 'kind' },
-        },
-      },
-    },
-  },
-  keymap = {
-    ['<Enter>'] = { 'select_and_accept', 'fallback' },
-    ['<C-u>'] = { 'scroll_documentation_up', 'fallback' },
-    ['<C-d>'] = { 'scroll_documentation_down', 'fallback' },
-  },
-})
-
--- typescript-vim compiler options
-vim.g.typescript_compiler_options = '--incremental --noEmit'
-
--- stuff
-_G.toggle_end_char = function(char)
-  local cursor = vim.api.nvim_win_get_cursor(0)
-  local row = cursor[1] - 1
-  local end_char = vim.api.nvim_buf_get_text(0, row, -2, row, -1, {})[1]
-  if end_char == char then
-    vim.api.nvim_buf_set_text(0, row, -2, row, -1, {})
-  else
-    vim.api.nvim_buf_set_text(0, row, -1, row, -1, { char })
-  end
-end

+ 36 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/completion.lua

@@ -0,0 +1,36 @@
+local blink_cmp = require('blink.cmp')
+
+blink_cmp.setup({
+  cmdline = {
+    enabled = false,
+  },
+  completion = {
+    accept = {
+      auto_brackets = {
+        enabled = false,
+      },
+    },
+    documentation = {
+      auto_show = true,
+      auto_show_delay_ms = 500,
+    },
+    trigger = {
+      prefetch_on_insert = false,
+      show_on_keyword = false,
+      show_on_trigger_character = false,
+    },
+    menu = {
+      draw = {
+        columns = {
+          { 'label', 'label_description', gap = 1 },
+          { 'kind' },
+        },
+      },
+    },
+  },
+  keymap = {
+    ['<Enter>'] = { 'select_and_accept', 'fallback' },
+    ['<C-u>'] = { 'scroll_documentation_up', 'fallback' },
+    ['<C-d>'] = { 'scroll_documentation_down', 'fallback' },
+  },
+})

+ 82 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/editing.lua

@@ -0,0 +1,82 @@
+local MiniAi = require('mini.ai')
+local MiniAlign = require('mini.align')
+local MiniComment = require('mini.comment')
+local MiniPairs = require('mini.pairs')
+local MiniSurround = require('mini.surround')
+
+-- comment actions
+MiniComment.setup()
+-- keymap: overrides default vim gc / gcc
+
+-- surround actions
+MiniSurround.setup({
+  custom_surroundings = {
+    -- js template string
+    ['$'] = {
+      output = {
+        left = '`${',
+        right = '}`',
+      },
+    },
+  },
+})
+-- keymap:
+-- sa (surround add)
+-- sr (surround replace)
+-- sd (surround delete)
+
+local spec_treesitter = require('mini.ai').gen_spec.treesitter
+
+MiniAi.setup({
+  -- only consider the current location
+  search_method = 'cover',
+  custom_textobjects = {
+    ['.'] = spec_treesitter({
+      a = '@call.outer',
+      i = '@call.inner',
+    }),
+    [','] = spec_treesitter({
+      a = '@parameter.outer',
+      i = '@parameter.inner',
+    }),
+  },
+});
+
+-- align actions
+MiniAlign.setup()
+-- keymap: ga / gA
+
+-- Treesitter-aware split/join
+require('treesj').setup({
+  use_default_keymaps = false,
+})
+
+vim.keymap.set('n', '<Leader>s', '<cmd>TSJSplit<CR>')
+vim.keymap.set('n', '<Leader>j', '<cmd>TSJJoin<CR>')
+
+-- autopair brackets
+MiniPairs.setup({
+  mappings = {
+    -- default config includes (, [ and {
+
+    -- autopair <> if preceded by a character, otherwise it might be a regular
+    -- comparison operation
+    ['<'] = { action = 'open', pair = '<>', neigh_pattern = '%w.' },
+    ['>'] = { action = 'close', pair = '<>' },
+  },
+})
+
+local function toggle_end_char(char)
+  local cursor = vim.api.nvim_win_get_cursor(0)
+  local row = cursor[1] - 1
+  local end_char = vim.api.nvim_buf_get_text(0, row, -2, row, -1, {})[1]
+  if end_char == char then
+    vim.api.nvim_buf_set_text(0, row, -2, row, -1, {})
+  else
+    vim.api.nvim_buf_set_text(0, row, -1, row, -1, { char })
+  end
+end
+
+-- add ,/; to end of line
+vim.keymap.set('i', '<C-,>', function() toggle_end_char(',') end)
+vim.keymap.set('i', '<C-;>', function() toggle_end_char(';') end)

+ 41 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/git.lua

@@ -0,0 +1,41 @@
+local MiniDiff = require('mini.diff')
+local util = require('user.util')
+
+-- shows added/removed/changed lines
+MiniDiff.setup({
+  -- keymap #bracketed
+  -- ]h / [h - navigate by hunks
+  view = {
+    style = 'sign',
+    signs = {
+      delete = '_',
+    },
+  },
+  source = {
+    MiniDiff.gen_source.git(),
+    -- handle fugitive paths
+    {
+      name = 'fugitive',
+      attach = function(buf_id)
+        local resolved = util.resolve_git_path(buf_id)
+        if not resolved or resolved.path == '' then
+          return false
+        end
+
+        local source = vim.fn.FugitiveFind(string.format('%s~1:%s', resolved.commit, resolved.path))
+        local text = vim.fn['fugitive#readfile'](source)
+        MiniDiff.set_ref_text(buf_id, text)
+      end
+    }
+  }
+})
+
+-- open status in current split without special flag (so navigating away closes it)
+-- https://github.com/tpope/vim-fugitive/issues/1296
+vim.keymap.set('n', '<Leader>gs', '<cmd>Gedit :<CR>')
+vim.keymap.set('n', '<Leader>gb', '<cmd>Telescope git_branches<CR>')
+vim.keymap.set('n', '<Leader>gd', MiniDiff.toggle_overlay)
+
+-- conflicts navigation #bracketed
+vim.keymap.set('n', '[x', '?^[<lt>=>]\\{7}<CR>')
+vim.keymap.set('n', ']x', '/^[<lt>=>]\\{7}<CR>')

+ 12 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/init.lua

@@ -0,0 +1,12 @@
+require('user.plugins.completion')
+require('user.plugins.editing')
+require('user.plugins.git')
+require('user.plugins.navigation')
+require('user.plugins.telescope')
+require('user.plugins.treesitter')
+require('user.plugins.ui')
+
+-- misc
+
+-- undotree
+vim.keymap.set('n', '<Leader>ut', '<cmd>UndotreeToggle<CR>')

+ 28 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/navigation.lua

@@ -0,0 +1,28 @@
+local MiniJump = require('mini.jump')
+local Treewalker = require('treewalker')
+
+-- repeatable f/t
+MiniJump.setup({
+  mappings = {
+    repeat_jump = '',
+  },
+  delay = {
+    highlight = 10000000,
+  },
+})
+
+-- Treesitter navigation
+Treewalker.setup()
+
+vim.keymap.set({ 'n', 'v' }, '<C-k>', '<cmd>Treewalker Up<cr>')
+vim.keymap.set({ 'n', 'v' }, '<C-j>', '<cmd>Treewalker Down<cr>')
+vim.keymap.set({ 'n', 'v' }, '<C-h>', '<cmd>Treewalker Left<cr>')
+vim.keymap.set({ 'n', 'v' }, '<C-l>', '<cmd>Treewalker Right<cr>')
+
+-- jumplist navigation #bracketed
+vim.keymap.set('n', '[f', '<C-O>')
+vim.keymap.set('n', ']f', '<C-I>')
+
+-- window navigation #bracketed
+vim.keymap.set('n', '[w', '<C-w>W')
+vim.keymap.set('n', ']w', '<C-w>w')

+ 167 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/telescope.lua

@@ -0,0 +1,167 @@
+-- file/buffer/etc picker
+local telescope = require('telescope')
+local telescope_actions = require('telescope.actions')
+local telescope_builtin = require('telescope.builtin')
+local telescope_pickers = require('telescope.pickers')
+local telescope_finders = require('telescope.finders')
+local telescope_previewers = require('telescope.previewers')
+local telescope_putils = require('telescope.previewers.utils')
+local telescope_conf = require('telescope.config').values
+local action_state = require('telescope.actions.state')
+local util = require('user.util')
+
+local actions = {}
+
+actions.git_show_commit = function(prompt_bufnr)
+  local selection = action_state.get_selected_entry()
+  local path = vim.fn.FugitiveFind(selection.value)
+  telescope_actions.close(prompt_bufnr)
+  vim.cmd.edit(path)
+end
+
+local commit_mappings = {
+  i = {
+    ['<CR>'] = actions.git_show_commit,
+    ['<C-r>c'] = telescope_actions.git_checkout,
+  }
+}
+
+telescope.setup({
+  defaults = {
+    mappings = {
+      i = {
+        ['jj'] = 'close',
+      },
+    },
+    layout_config = {
+      prompt_position = 'top',
+    },
+    sorting_strategy = 'ascending',
+    -- use filename as preview window title
+    dynamic_preview_title = true,
+
+    path_display = {
+      -- shorten directory names of everything but the last 3 parts
+      -- foo/bar/baz/file.txt -> f/boo/bar/file.txt
+      shorten = { len = 1, exclude = { -3, -2, -1 } },
+
+      -- truncate the beginning of the file name if wider than the window
+      truncate = true,
+    },
+
+    preview = {
+      -- don't preview files larger than 1MB
+      filesize_limit = 1,
+      timeout = 500,
+    },
+
+    vimgrep_arguments = {
+      -- defaults
+      'rg',
+      '--color=never',
+      '--no-heading',
+      '--with-filename',
+      '--line-number',
+      '--column',
+      '--smart-case',
+      -- search "hidden" files except git folder
+      '--hidden',
+      '--iglob=!.git'
+    },
+
+    -- ignore things we're likely not to edit
+    file_ignore_patterns = {
+      '%.zip$',
+      '%.yarn/releases/',
+      '%.yarn/plugins/'
+    },
+
+    -- picker history
+    cache_picker = {
+      num_pickers = 10,
+    },
+  },
+  pickers = {
+    buffers = {
+      sort_lastused = true,
+      sort_mru = true,
+      mappings = {
+        i = {
+          ['<C-k>'] = 'delete_buffer'
+        },
+      },
+    },
+    find_files = {
+      find_command = { 'fd', '--type', 'f', '--strip-cwd-prefix' }
+    },
+    git_commits = {
+      mappings = commit_mappings,
+    },
+    git_bcommits = {
+      mappings = commit_mappings,
+    },
+  },
+})
+
+-- use native sorter for better performance
+telescope.load_extension('fzf')
+
+-- custom picker to fallback to files if no git
+local function project_files()
+  local ok = pcall(telescope_builtin.git_files, { show_untracked = true })
+  if not ok then telescope_builtin.find_files({}) end
+end
+
+-- custom picker for files within a commit
+local function commit_files(opts)
+  local resolved = util.resolve_git_path(0)
+  if not resolved then
+    vim.print('current file is not a fugitive path')
+    return
+  end
+
+  opts = opts or {}
+  telescope_pickers.new(opts, {
+    prompt_title = resolved.commit,
+    finder = telescope_finders.new_oneshot_job({ 'git', 'ls-tree', '--name-only', '-r', resolved.commit }, {
+      entry_maker = function(entry)
+        local path = string.format('fugitive://%s//%s/%s', resolved.repo, resolved.commit, entry)
+        return {
+          path = path,
+          value = entry,
+          display = entry,
+          ordinal = entry,
+        }
+      end,
+    }),
+    sorter = telescope_conf.file_sorter(opts),
+
+    -- the builtin previewer has fancy async loading which doesn't work for
+    -- fugitive paths so we have to define our own
+    previewer = telescope_previewers.new_buffer_previewer({
+      title = function(self)
+        return 'Commit Files'
+      end,
+      dyn_title = function(self, entry)
+        return entry.value
+      end,
+      define_preview = function(self, entry, status)
+        -- the builtin previewer does more things like using mime type
+        -- fallbacks as well as binary file detection which ours doesn't do
+        local ft = telescope_putils.filetype_detect(entry.value)
+
+        vim.api.nvim_buf_call(self.state.bufnr, function()
+          vim.cmd('Gread ' .. entry.path)
+          telescope_putils.highlighter(self.state.bufnr, ft, opts)
+        end)
+      end,
+    }),
+  }):find()
+end
+
+vim.keymap.set('n', '<C-P>', project_files)
+vim.keymap.set('n', '<C-O>', '<cmd>Telescope buffers<CR>')
+vim.keymap.set('n', '<Leader>fg', commit_files)
+vim.keymap.set('n', '<Leader>ff', '<cmd>Telescope live_grep<CR>')
+vim.keymap.set('n', '<Leader>fr', '<cmd>Telescope resume<CR>')
+vim.keymap.set('n', '<Leader>fp', '<cmd>Telescope pickers<CR>')

+ 39 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/treesitter.lua

@@ -0,0 +1,39 @@
+-- Use Treesitter for syntax highlighting
+require('nvim-treesitter.configs').setup({
+  highlight = {
+    enable = true,
+  },
+  indent = {
+    enable = true,
+  },
+  incremental_selection = {
+    enable = true,
+    keymaps = {
+      init_selection = ']t',
+      node_incremental = ']t',
+      node_decremental = '[t',
+    },
+  },
+  textobjects = {
+    swap = {
+      enable = true,
+      swap_next = {
+        ['>,'] = '@parameter.inner',
+      },
+      swap_previous = {
+        ['<,'] = '@parameter.inner',
+      },
+    },
+  },
+})
+
+-- set foldexpr to use treesitter
+vim.o.foldmethod = 'expr'
+vim.o.foldexpr = 'nvim_treesitter#foldexpr()'
+vim.o.foldenable = false
+
+-- Treesitter context
+require('treesitter-context').setup({
+  enable = true,
+  multiline_threshold = 5,
+})

+ 63 - 0
.config/nixpkgs/neovim/config/lua/user/plugins/ui.lua

@@ -0,0 +1,63 @@
+local MiniBufremove = require('mini.bufremove')
+local MiniFiles = require('mini.files')
+local MiniIndentscope = require('mini.indentscope')
+local MiniNotify = require('mini.notify')
+local MiniStatusline = require('mini.statusline')
+
+local function section_git(args)
+  if MiniStatusline.is_truncated(args.trunc_width) then return '' end
+  return vim.fn.FugitiveHead()
+end
+
+MiniStatusline.setup({
+  content = {
+    -- copy-pasted from default, we just want to remove the icon
+    active = function()
+      local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 120 })
+      local git           = section_git({ trunc_width = 75 })
+      local diagnostics   = MiniStatusline.section_diagnostics({ trunc_width = 75, icon = '' })
+      local filename      = MiniStatusline.section_filename({ trunc_width = 140 })
+      local fileinfo      = MiniStatusline.section_fileinfo({ trunc_width = 120 })
+      local location      = MiniStatusline.section_location({ trunc_width = 75 })
+
+      return MiniStatusline.combine_groups({
+        { hl = mode_hl,                 strings = { mode } },
+        { hl = 'MiniStatuslineDevinfo', strings = { git, diagnostics } },
+        '%<', -- Mark general truncate point
+        { hl = 'MiniStatuslineFilename', strings = { filename } },
+        '%=', -- End left alignment
+        { hl = 'MiniStatuslineFileinfo', strings = { fileinfo } },
+        { hl = mode_hl,                  strings = { location } },
+      })
+    end
+  },
+})
+
+-- notifications
+MiniNotify.setup()
+vim.notify = MiniNotify.make_notify()
+
+vim.keymap.set('n', '<Leader>n', MiniNotify.show_history)
+
+-- file explorer
+MiniFiles.setup({
+  content = {
+    -- remove icons
+    prefix = function() end,
+  }
+})
+
+vim.keymap.set('n', '<Leader>ft', function() MiniFiles.open(MiniFiles.get_latest_path()) end)
+vim.keymap.set('n', '<Leader>fc', function() MiniFiles.open(vim.api.nvim_buf_get_name(0)) end)
+
+-- delete buffer while preserving layout
+MiniBufremove.setup()
+
+vim.keymap.set('n', '<Leader>q', MiniBufremove.delete)
+vim.keymap.set('n', '<Leader>Q', function() MiniBufremove.delete(0, true) end)
+
+-- shows a line indicating the current indentation scope
+MiniIndentscope.setup()
+
+-- cursor display
+vim.keymap.set('n', '<Leader>c', '<cmd>set cursorline! cursorcolumn!<CR>')

+ 35 - 5
.config/nixpkgs/neovim/config/lua/user/settings.lua

@@ -50,9 +50,39 @@ vim.o.inccommand = 'split'
 -- mouse only in visual mode
 vim.o.mouse = 'v'
 
--- set foldexpr to use treesitter
-vim.o.foldmethod = 'expr'
-vim.o.foldexpr = 'nvim_treesitter#foldexpr()'
-vim.o.foldenable = false
-
+-- use POSIX-y shell for !
 vim.o.shell = '/bin/sh'
+
+-- basic keymaps
+-- ; as :
+vim.keymap.set({ 'n', 'v' }, ';', ':')
+
+-- leave insert mode via jj
+vim.keymap.set('i', 'jj', '<ESC>')
+
+-- j/k with wraps
+vim.keymap.set({ 'n', 'v' }, 'j', 'gj')
+vim.keymap.set({ 'n', 'v' }, 'k', 'gk')
+
+-- select pasted text
+vim.keymap.set('n', 'gp', '`[v`]')
+
+-- format on save
+vim.api.nvim_create_autocmd('BufWritePre', {
+  callback = function(opts)
+    if vim.bo.filetype == 'diff' then
+      return
+    end
+
+    if not vim.g.no_lsp_format then
+      -- check if can LSP format
+      local clients = vim.lsp.get_clients({ bufnr = opts.buf, method = 'textDocument/formatting' })
+      if #clients > 0 then
+        vim.lsp.buf.format({ bufnr = opts.buf })
+      end
+    end
+
+    -- otherwise strip trailing whitespace
+    vim.cmd('%s/\\s\\+$//e')
+  end,
+})

+ 41 - 0
.config/nixpkgs/neovim/config/lua/user/terminal.lua

@@ -0,0 +1,41 @@
+-- apply default terminal settings
+vim.api.nvim_create_autocmd('TermOpen', {
+  callback = function()
+    vim.bo.scrollback = 10000
+    vim.opt_local.number = false
+    vim.opt_local.relativenumber = false
+    vim.b.miniindentscope_disable = true
+  end
+})
+
+-- preserve window structure when exiting terminal via C-d
+vim.api.nvim_create_autocmd('TermClose', {
+  callback = function(opts)
+    -- don't trigger when force deleting
+    if vim.api.nvim_buf_get_option(opts.buf, 'modified') then
+      return
+    end
+    MiniBufremove.delete(opts.buf)
+  end,
+  -- needed so statusline properly updates
+  nested = true,
+})
+
+-- automatically enter/leave terminal mode
+vim.api.nvim_create_autocmd('TermOpen', { command = 'startinsert' })
+vim.api.nvim_create_autocmd({ 'WinEnter', 'BufWinEnter' }, {
+  pattern = 'term://*',
+  command = 'startinsert',
+})
+vim.api.nvim_create_autocmd('BufLeave', {
+  pattern = 'term://*',
+  command = 'stopinsert',
+})
+
+-- leave insert mode with <ESC><ESC>
+vim.keymap.set('t', '<ESC><ESC>', '<C-\\><C-N>')
+vim.keymap.set('t', '<C-[><C-[>', '<C-\\><C-N>')
+
+-- opening terminals
+vim.keymap.set('n', '<Leader>tv', '<cmd>vertical term $SHELL<CR>')
+vim.keymap.set('n', '<Leader>to', '<cmd>term $SHELL<CR>')

+ 40 - 0
.config/nixpkgs/neovim/config/lua/user/util.lua

@@ -0,0 +1,40 @@
+local M = {}
+
+function M.resolve_git_path(buf_id)
+  local bufname = vim.api.nvim_buf_get_name(buf_id)
+  if not vim.startswith(bufname, 'fugitive://') then
+    return false
+  end
+
+  local parsed = vim.fn.FugitiveParse(bufname)
+  local resolved_path = parsed[1]
+  local repo = parsed[2]
+
+  if resolved_path == '' then
+    return false
+  end
+
+  local parts = vim.split(resolved_path, ':')
+  local commit = parts[1]
+  local path = parts[2]
+
+  return {
+    repo = repo,
+    commit = commit,
+    path = path,
+  }
+end
+
+function M.read_helix_config()
+  local tinytoml = require('tinytoml')
+  local data = vim.secure.read('.helix/languages.toml')
+  if data ~= nil then
+    local status, data = pcall(tinytoml.parse, data, { load_from_string = true })
+    if status then
+      return data
+    end
+  end
+  return {}
+end
+
+return M

+ 6 - 0
.config/nixpkgs/neovim/config/plugin/elvish.lua

@@ -0,0 +1,6 @@
+-- add extra filetypes for plenary
+require('plenary.filetype').add_table({
+  extension = {
+    ['elv'] = [[elvish]]
+  }
+})