├── .envrc ├── nvim ├── spell │ ├── de.utf-8.add │ ├── de.utf-8.add.spl │ ├── en.utf-8.add.spl │ └── en.utf-8.add ├── after │ └── ftplugin │ │ ├── help.lua │ │ ├── cabal.lua │ │ ├── bash.lua │ │ ├── css.lua │ │ ├── diff.lua │ │ ├── html.lua │ │ ├── jq.lua │ │ ├── make.lua │ │ ├── nu.lua │ │ ├── gitcommit.lua │ │ ├── graphql.lua │ │ ├── markdown.lua │ │ ├── proto.lua │ │ ├── scala.lua │ │ ├── scheme.lua │ │ ├── terraform.lua │ │ ├── typst.lua │ │ ├── vimdoc.lua │ │ ├── dockerfile.lua │ │ ├── editorconfig.lua │ │ ├── latex.lua │ │ ├── tex.lua │ │ ├── c.lua │ │ ├── cpp.lua │ │ ├── thrift.lua │ │ ├── dhall.lua │ │ ├── sql.lua │ │ ├── typescript.lua │ │ ├── json.lua │ │ ├── sh.lua │ │ ├── vim.lua │ │ ├── python.lua │ │ ├── yaml.lua │ │ ├── nix.lua │ │ ├── lua.lua │ │ ├── toml.lua │ │ ├── java.lua │ │ ├── haskell.lua │ │ └── rust.lua ├── plugin │ ├── misc.lua │ ├── term-edit.lua │ ├── which-key.lua │ ├── abbrev.lua │ ├── eyeliner.lua │ ├── setups.lua │ ├── fff.lua │ ├── wordmotion.lua │ ├── neovide.lua │ ├── oil.lua │ ├── statuscol.lua │ ├── dial.lua │ ├── vim-matchup.lua │ ├── deprecation-warnings.lua │ ├── substitute.lua │ ├── highlight-colors.lua │ ├── ftdetect.lua │ ├── yanky.lua │ ├── commands.lua │ ├── completion.lua │ ├── flash.lua │ ├── harpoon.lua │ ├── comment.lua │ ├── persistence.lua │ ├── terminal.lua │ ├── gitlinker.lua │ ├── qf.lua │ ├── gitsigns.lua │ ├── other.lua │ ├── theme.lua │ ├── telescope.lua │ ├── keymaps.lua │ └── autocommands.lua ├── queries │ ├── python │ │ └── injections.scm │ ├── lua │ │ └── injections.scm │ ├── haskell │ │ ├── injections.scm │ │ └── highlights.scm │ └── nix │ │ └── injections.scm ├── syntax │ └── qf.vim └── init.lua ├── .gitignore ├── .stylua.toml ├── .luacheckrc ├── lib └── lua │ ├── mrcjk │ ├── lsp │ │ ├── init.lua │ │ └── codelens.lua │ ├── neotest.lua │ ├── files.lua │ └── indentexpr.lua │ └── lang │ ├── haskell.lua │ ├── texlab.lua │ └── clangd.lua ├── README.md ├── .github └── workflows │ ├── auto-merge.yml │ ├── nix-build.yml │ └── update.yml ├── nix ├── plugin-overlay.nix └── neovim-overlay.nix ├── flake.nix └── LICENSE /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /nvim/spell/de.utf-8.add: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nvim/spell/de.utf-8.add.spl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/help.lua: -------------------------------------------------------------------------------- 1 | vim.treesitter.start() 2 | -------------------------------------------------------------------------------- /nvim/plugin/misc.lua: -------------------------------------------------------------------------------- 1 | vim.g.sqlite_clib_path = vim.env.LIBSQLITE 2 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/cabal.lua: -------------------------------------------------------------------------------- 1 | require('lang.haskell').set_keymaps() 2 | -------------------------------------------------------------------------------- /nvim/plugin/term-edit.lua: -------------------------------------------------------------------------------- 1 | require('term-edit').setup { 2 | prompt_end = '> ', 3 | } 4 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/bash.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/css.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/diff.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/html.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/jq.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/make.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/nu.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/gitcommit.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/graphql.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/markdown.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/proto.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/scala.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/scheme.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/terraform.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/typst.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/vimdoc.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/spell/en.utf-8.add.spl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrcjkb/nvim/HEAD/nvim/spell/en.utf-8.add.spl -------------------------------------------------------------------------------- /nvim/after/ftplugin/dockerfile.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/editorconfig.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | .luarc.json 3 | .pre-commit-config.yaml 4 | .luarc.json 5 | result 6 | time.txt 7 | tags 8 | -------------------------------------------------------------------------------- /nvim/plugin/which-key.lua: -------------------------------------------------------------------------------- 1 | local which_key = require('which-key') 2 | which_key.setup { 3 | preset = 'helix', 4 | } 5 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/latex.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | require('lang.texlab').launch() 5 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/tex.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start('latex') 3 | 4 | require('lang.texlab').launch() 5 | -------------------------------------------------------------------------------- /nvim/plugin/abbrev.lua: -------------------------------------------------------------------------------- 1 | -- Common typos 2 | 3 | vim.cmd.abbrev { 'produciton ', 'production' } 4 | vim.cmd.abbrev { 'Produciton ', 'Production' } 5 | -------------------------------------------------------------------------------- /nvim/plugin/eyeliner.lua: -------------------------------------------------------------------------------- 1 | require('eyeliner').setup { 2 | highlight_on_key = true, -- show highlights only after keypress 3 | dim = true, -- dim all other characters 4 | } 5 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | line_endings = "Unix" 2 | indent_type = "Spaces" 3 | indent_width = 2 4 | quote_style = "AutoPreferSingle" 5 | call_parentheses = "NoSingleTable" 6 | # collapse_simple_statement = "Never" 7 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/c.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | if vim.fn.executable('clangd') ~= 1 then 5 | return 6 | end 7 | 8 | require('lang.clangd').launch() 9 | -------------------------------------------------------------------------------- /nvim/plugin/setups.lua: -------------------------------------------------------------------------------- 1 | local function setup(mod) 2 | require(mod).setup() 3 | end 4 | 5 | if not vim.g.nvim_surround_setup then 6 | setup('nvim-surround') 7 | vim.g.nvim_surround_setup = true 8 | end 9 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/cpp.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start('cpp') 3 | 4 | if vim.fn.executable('clangd') ~= 1 then 5 | return 6 | end 7 | 8 | require('lang.clangd').launch() 9 | -------------------------------------------------------------------------------- /nvim/plugin/fff.lua: -------------------------------------------------------------------------------- 1 | vim.keymap.set('n', 'ff', vim.cmd.FFFFind, { noremap = true, silent = true, desc = '[f]ff [f]ind' }) 2 | vim.keymap.set('n', 'tg', vim.cmd.FFFFind, { noremap = true, silent = true, desc = '[f]ff [f]ind' }) 3 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | ignore = { 2 | '631', -- max_line_length 3 | '122', -- read-only field of global variable 4 | } 5 | read_globals = { 6 | 'vim', 7 | 'describe', 8 | 'it', 9 | 'assert', 10 | 'stub', 11 | 'Toggle_venn', 12 | } 13 | -------------------------------------------------------------------------------- /nvim/plugin/wordmotion.lua: -------------------------------------------------------------------------------- 1 | -- Use Alt as prefix for word motion mappings 2 | vim.g.wordmotion_mappings = { 3 | ['w'] = '', 4 | ['b'] = '', 5 | ['e'] = '', 6 | ['ge'] = 'g', 7 | ['aw'] = 'av', 8 | ['iw'] = 'iv', 9 | [''] = '', 10 | } 11 | -------------------------------------------------------------------------------- /nvim/plugin/neovide.lua: -------------------------------------------------------------------------------- 1 | if not vim.g.neovide then 2 | return 3 | end 4 | 5 | local font_size = 12 6 | vim.o.guifont = 'JetBrains Mono Nerd Font Mono' .. ':h' .. font_size 7 | vim.g.neovide_hide_mouse_when_typing = true 8 | -- vim.g.neovide_underline_automatic_scaling = true -- Noticeable for font sizes above 15 9 | -------------------------------------------------------------------------------- /lib/lua/mrcjk/lsp/init.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: cast-local-type 2 | local lsp = {} 3 | 4 | local capabilities = vim.lsp.protocol.make_client_capabilities() 5 | 6 | ---@type lsp.ClientCapabilities 7 | ---@diagnostic disable-next-line: assign-type-mismatch 8 | lsp.capabilities = capabilities 9 | 10 | return lsp 11 | -------------------------------------------------------------------------------- /nvim/queries/python/injections.scm: -------------------------------------------------------------------------------- 1 | ;; extends 2 | 3 | ;; nixosTest 4 | 5 | (call 6 | function: (attribute 7 | object: (_) 8 | attribute: (identifier) @_func_name) 9 | arguments: (argument_list 10 | (string) @injection.content (#set! injection.language "bash")) 11 | (#any-of? @_func_name "wait_until_succeeds" "succeed")) 12 | -------------------------------------------------------------------------------- /nvim/plugin/oil.lua: -------------------------------------------------------------------------------- 1 | local oil = require('oil') 2 | 3 | oil.setup { 4 | view_options = { 5 | -- Show files and directories that start with "." 6 | show_hidden = true, 7 | }, 8 | win_options = { 9 | signcolumn = 'yes:2', 10 | }, 11 | } 12 | 13 | vim.keymap.set('n', '-', function() 14 | vim.cmd.Oil() 15 | end, { desc = 'open parent directory' }) 16 | -------------------------------------------------------------------------------- /nvim/plugin/statuscol.lua: -------------------------------------------------------------------------------- 1 | local builtin = require('statuscol.builtin') 2 | require('statuscol').setup { 3 | setopt = true, 4 | relculright = true, 5 | segments = { 6 | { text = { '%s' }, click = 'v:lua.ScSa' }, 7 | { 8 | text = { builtin.lnumfunc, ' ' }, 9 | condition = { true, builtin.not_empty }, 10 | click = 'v:lua.ScLa', 11 | }, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My Neovim Nix derivations 2 | 3 | Can be run using 4 | 5 | ```console 6 | nix run "github:mrcjkb/nvim" 7 | ``` 8 | 9 | See also: 10 | 11 | - [My NixOS config](https://github.com/mrcjkb/nixfiles/) 12 | - [kickstart-nix.nvim](https://github.com/mrcjkb/kickstart-nix.nvim): 13 | A simple Nix flake template repository for Neovim 14 | derivations, based on this config. 15 | -------------------------------------------------------------------------------- /nvim/queries/lua/injections.scm: -------------------------------------------------------------------------------- 1 | ;; extends 2 | 3 | ; ((string ("string_content") @query) (#lua-match? @query "^%s*;;+%s?")) 4 | 5 | ;; TODO: 6 | ; ((comment 7 | ; content: (comment_content) @injection.language) 8 | ; . (expression_list 9 | ; value: (string content: (string_content) @injection.content)) 10 | ; (#gsub! @injection.language "%-%-[[%s*([%w%p]+)%s*%]%]" "%1") 11 | ; ) 12 | -------------------------------------------------------------------------------- /nvim/plugin/dial.lua: -------------------------------------------------------------------------------- 1 | require('lz.n').load { 2 | 'dial.nvim', 3 | ---@type lz.n.KeysSpec[] 4 | keys = { 5 | { '', '(dial-increment)', mode = { 'n', 'v' } }, 6 | { '', '(dial-decrement)', mode = { 'n', 'v' } }, 7 | { 'g', 'g(dial-increment)', mode = { 'n', 'v' } }, 8 | { 'g', 'g(dial-decrement)', mode = { 'n', 'v' } }, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /nvim/plugin/vim-matchup.lua: -------------------------------------------------------------------------------- 1 | if vim.g.vim_matchup_setup_done then 2 | return 3 | end 4 | vim.api.nvim_create_autocmd('FileType', { 5 | once = true, 6 | pattern = '*', 7 | group = vim.api.nvim_create_augroup('vim_matchup-setup', {}), 8 | callback = function() 9 | if vim.g.vim_matchup_setup_done then 10 | return 11 | end 12 | vim.g.vim_matchup_setup_done = true 13 | vim.cmd.packadd('vim-matchup') 14 | end, 15 | }) 16 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/thrift.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | if vim.fn.executable('thriftls') ~= 1 then 5 | return 6 | end 7 | 8 | local lsp = require('mrcjk.lsp') 9 | 10 | ---@diagnostic disable-next-line: missing-fields 11 | vim.lsp.start { 12 | cmd = { 'thriftls' }, 13 | root_dir = vim.fs.dirname(vim.fs.find({ '.git' }, { upward = true })[1]), 14 | filetypes = { 'thrift' }, 15 | capabilities = lsp.capabilities, 16 | } 17 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/dhall.lua: -------------------------------------------------------------------------------- 1 | local lsp = require('mrcjk.lsp') 2 | local files = require('mrcjk.files') 3 | 4 | files.treesitter_start() 5 | 6 | if vim.fn.executable('dhall-lsp-server') ~= 1 then 7 | return 8 | end 9 | 10 | ---@diagnostic disable-next-line: missing-fields 11 | vim.lsp.start { 12 | cmd = { 'dhall-lsp-server' }, 13 | root_dir = vim.fs.dirname(vim.fs.find({ '.git' }, { upward = true })[1]), 14 | filetypes = { 'dhall' }, 15 | capabilities = lsp.capabilities, 16 | } 17 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/sql.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local lsp = require('mrcjk.lsp') 5 | 6 | if vim.fn.executable('sqls') ~= 1 then 7 | return 8 | end 9 | 10 | ---@diagnostic disable-next-line: missing-fields 11 | vim.lsp.start { 12 | cmd = { 'sqls' }, 13 | root_dir = vim.fs.dirname(vim.fs.find({ '.git', 'config.yml' }, { upward = true })[1]), 14 | filetypes = { 'sql' }, 15 | capabilities = lsp.capabilities, 16 | settings = {}, 17 | } 18 | -------------------------------------------------------------------------------- /nvim/plugin/deprecation-warnings.lua: -------------------------------------------------------------------------------- 1 | local disabled_deprecation_warnings = { 2 | 'vim.treesitter.query', 3 | 'vim.treesitter.parse_query', 4 | 'for_each_child', 5 | } 6 | 7 | local orig_deprecate = vim.deprecate 8 | 9 | local filter_deprecate = function(name, ...) 10 | for _, disabled in pairs(disabled_deprecation_warnings) do 11 | if name:find(disabled, 1, true) then 12 | return 13 | end 14 | end 15 | orig_deprecate(name, ...) 16 | end 17 | 18 | vim.deprecate = filter_deprecate 19 | -------------------------------------------------------------------------------- /nvim/plugin/substitute.lua: -------------------------------------------------------------------------------- 1 | local substitute = require('substitute') 2 | substitute.setup { 3 | on_substitute = require('yanky.integration').substitute(), 4 | } 5 | 6 | vim.keymap.set('n', 's', substitute.operator, { noremap = true, desc = 'Substitute (operator)' }) 7 | vim.keymap.set('n', 'ss', substitute.line, { noremap = true, desc = 'Substitute (line)' }) 8 | vim.keymap.set('n', 'S', substitute.eol, { noremap = true, desc = 'Substitute (eol)' }) 9 | vim.keymap.set('x', 's', substitute.visual, { noremap = true, desc = 'Substitute (visual)' }) 10 | -------------------------------------------------------------------------------- /nvim/plugin/highlight-colors.lua: -------------------------------------------------------------------------------- 1 | if vim.g.did_highlight_colors_plugin then 2 | return 3 | end 4 | 5 | local did_setup = false 6 | 7 | vim.keymap.set('n', 'ct', function() 8 | local hc 9 | if not did_setup then 10 | hc = require('nvim-highlight-colors') 11 | hc.setup {} 12 | hc.turnOff() 13 | did_setup = true 14 | else 15 | hc = require('nvim-highlight-colors') 16 | end 17 | hc.toggle() 18 | end, { noremap = true, silent = true, desc = 'highlight [c]olors: [t]oggle' }) 19 | 20 | vim.g.did_highlight_colors_plugin = true 21 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/typescript.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local lsp = require('mrcjk.lsp') 5 | 6 | if vim.fn.executable('typescript-language-server') ~= 1 then 7 | return 8 | end 9 | 10 | local root_files = { 11 | 'package.json', 12 | '.git', 13 | } 14 | 15 | vim.lsp.start { 16 | name = 'tsls', 17 | cmd = { 'typescript-language-server', '--stdio' }, 18 | root_dir = vim.fs.dirname(vim.fs.find(root_files, { upward = true })[1]), 19 | filetypes = { 'typescript' }, 20 | capabilities = lsp.capabilities, 21 | } 22 | -------------------------------------------------------------------------------- /nvim/plugin/ftdetect.lua: -------------------------------------------------------------------------------- 1 | vim.filetype.add { 2 | extension = { 3 | avsc = 'json', -- avro 4 | jsonc = 'json', 5 | 6 | -- NOTE: Example for a more complex filetype detection... 7 | -- SEE: https://www.reddit.com/r/neovim/comments/rvwsl3/introducing_filetypelua_and_a_call_for_help/ 8 | -- h = function() 9 | -- -- Use a lazy heuristic that #including a C++ header means it's a 10 | -- -- C++ header 11 | -- if vim.fn.search('\\C^#include <[^>.]\\+>$', 'nw') == 1 then 12 | -- return 'cpp' 13 | -- end 14 | -- return 'c' 15 | -- end, 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/json.lua: -------------------------------------------------------------------------------- 1 | local lsp = require('mrcjk.lsp') 2 | 3 | local files = require('mrcjk.files') 4 | files.treesitter_start() 5 | 6 | if vim.fn.executable('vscode-json-languageserver') ~= 1 then 7 | return 8 | end 9 | 10 | ---@diagnostic disable-next-line: missing-fields 11 | vim.lsp.start { 12 | name = 'jsonls', 13 | cmd = { 'vscode-json-languageserver', '--stdio' }, 14 | capabilities = lsp.capabilities, 15 | filetypes = { 'json' }, 16 | settings = { 17 | json = { 18 | schemas = require('schemastore').json.schemas(), 19 | validate = { enable = true }, 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/sh.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | local lsp = require('mrcjk.lsp') 3 | 4 | files.treesitter_start('bash') 5 | 6 | if vim.fn.executable('bash-language-server') ~= 1 then 7 | return 8 | end 9 | 10 | ---@diagnostic disable-next-line: missing-fields 11 | vim.lsp.start { 12 | cmd = { 'bash-language-server', 'start' }, 13 | root_dir = vim.fs.dirname(vim.fs.find({ '.git' }, { upward = true })[1]), 14 | capabilities = lsp.capabilities, 15 | filetypes = { 'sh' }, 16 | settings = { 17 | bashIde = { 18 | globPattern = vim.env.GLOB_PATTERN or '*@(.sh|.inc|.bash|.command)', 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /nvim/plugin/yanky.lua: -------------------------------------------------------------------------------- 1 | require('yanky').setup { 2 | highlight = { 3 | on_put = true, 4 | on_yank = true, 5 | timer = 500, 6 | }, 7 | preserve_cursor_position = { 8 | enabled = true, 9 | }, 10 | } 11 | vim.keymap.set({ 'n', 'x' }, 'p', '(YankyPutAfter)') 12 | vim.keymap.set({ 'n', 'x' }, 'P', '(YankyPutBefore)') 13 | vim.keymap.set({ 'n', 'x' }, 'gp', '(YankyGPutAfter)') 14 | vim.keymap.set({ 'n', 'x' }, 'gP', '(YankyGPutBefore)') 15 | vim.keymap.set('n', '', '(YankyCycleForward)') 16 | vim.keymap.set('n', '', '(YankyCycleBackward)') 17 | vim.keymap.set({ 'n', 'x' }, 'y', '(YankyYank)') 18 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dependabot auto-approve 3 | on: pull_request_target 4 | 5 | permissions: 6 | contents: write 7 | pull-requests: write 8 | 9 | jobs: 10 | dependabot: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.actor == 'dependabot[bot]' }} 13 | steps: 14 | - name: Dependabot metadata 15 | id: metadata 16 | uses: dependabot/fetch-metadata@v1 17 | with: 18 | github-token: "${{ secrets.GH_TOKEN_FOR_UPDATES }}" 19 | - name: Approve PR 20 | run: gh pr review --approve "$PR_URL" 21 | env: 22 | PR_URL: ${{github.event.pull_request.html_url}} 23 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN_FOR_UPDATES }} 24 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/vim.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local lsp = require('mrcjk.lsp') 5 | 6 | if vim.fn.executable('vimls') ~= 1 then 7 | return 8 | end 9 | 10 | vim.lsp.start { 11 | name = 'vimls', 12 | cmd = { 'vim-language-server', '--stdio' }, 13 | filetypes = { 'vim' }, 14 | capabilities = lsp.capabilities, 15 | init_options = { 16 | isNeovim = true, 17 | iskeyword = '@,48-57,_,192-255,-#', 18 | vimruntime = '', 19 | runtimepath = '', 20 | diagnostic = { enable = true }, 21 | indexes = { 22 | runtimepath = true, 23 | gap = 100, 24 | count = 3, 25 | projectRootPatterns = { 'runtime', 'nvim', '.git', 'autoload', 'plugin' }, 26 | }, 27 | suggest = { fromVimruntime = true, fromRuntimepath = true }, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /nvim/syntax/qf.vim: -------------------------------------------------------------------------------- 1 | if exists('b:current_syntax') 2 | finish 3 | endif 4 | 5 | syn match qfFileName /^[^│]*/ nextgroup=qfSeparatorLeft 6 | syn match qfSeparatorLeft /│/ contained nextgroup=qfLineNr 7 | syn match qfLineNr /[^│]*/ contained nextgroup=qfSeparatorRight 8 | syn match qfSeparatorRight '│' contained nextgroup=qfError,qfWarning,qfInfo,qfNote 9 | syn match qfError / E .*$/ contained 10 | syn match qfWarning / W .*$/ contained 11 | syn match qfInfo / I .*$/ contained 12 | syn match qfNote / [NH] .*$/ contained 13 | 14 | hi def link qfFileName Directory 15 | hi def link qfSeparatorLeft Delimiter 16 | hi def link qfSeparatorRight Delimiter 17 | hi def link qfLineNr LineNr 18 | hi def link qfError DiagnosticError 19 | hi def link qfWarning DiagnosticWarn 20 | hi def link qfInfo DiagnosticInfo 21 | hi def link qfNote DiagnosticHint 22 | 23 | let b:current_syntax = 'qf' 24 | -------------------------------------------------------------------------------- /nvim/plugin/commands.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | api.nvim_create_user_command('DeleteOtherBufs', '%bd|e#', {}) 4 | -- delete current buffer 5 | api.nvim_create_user_command('Q', function() 6 | vim.cmd.bd('%') 7 | end, {}) 8 | api.nvim_create_user_command('W', function() 9 | vim.cmd.w() 10 | end, {}) 11 | 12 | -- Pandoc shortcut 13 | api.nvim_create_user_command( 14 | 'Pd', 15 | 'split | resize 10 | terminal pandoc % -f markdown-implicit_figures -s -o ', 16 | { nargs = '*' } 17 | ) 18 | 19 | api.nvim_create_user_command('LspStop', function(kwargs) 20 | local name = kwargs.fargs[1] 21 | for _, client in pairs(vim.lsp.get_clients { buffer = 0 }) do 22 | if client.name == name then 23 | client:stop(true) 24 | end 25 | end 26 | end, { 27 | nargs = 1, 28 | complete = function() 29 | return vim.tbl_map(function(c) 30 | return c.name 31 | end, vim.lsp.get_clients { buffer = 0 }) 32 | end, 33 | }) 34 | -------------------------------------------------------------------------------- /nvim/plugin/completion.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: undefined-doc-param 2 | 3 | --- Implements custom completion for 'completefunc' 4 | --- 5 | --- - See |complete-functions| 6 | --- - See |complete-items| 7 | --- - See |CompleteDone| 8 | --- 9 | --- @param findstart integer 0 or 1, decides behavior 10 | --- @param base integer findstart=0, text to match against 11 | --- 12 | --- @return integer|table Decided by {findstart}: 13 | --- - findstart=0: column where the completion starts, or -2 or -3 14 | --- - findstart=1: list of matches (actually just calls |complete()|) 15 | -- function _G.completefunc(findstart, base) 16 | -- function _G.completefunc() 17 | -- return { 'a', 'b', 'c' } 18 | -- 19 | -- Return -2 to signal that we should continue completion so that we can 20 | -- async complete. 21 | -- return -2 22 | -- end 23 | 24 | -- in async complete: vim.fn.complete(start_col, matches) 25 | 26 | -- vim.o.completefunc = 'v:lua._G.completefunc' 27 | -------------------------------------------------------------------------------- /nvim/plugin/flash.lua: -------------------------------------------------------------------------------- 1 | local flash = require('flash') 2 | 3 | flash.setup { 4 | modes = { 5 | search = { 6 | enabled = false, 7 | }, 8 | char = { 9 | enabled = false, 10 | }, 11 | }, 12 | search = { 13 | exclude = { 14 | 'flash_prompt', 15 | function(win) 16 | -- exclude non-focusable windows 17 | return not vim.api.nvim_win_get_config(win).focusable 18 | end, 19 | }, 20 | }, 21 | label = { 22 | rainbow = { 23 | enabled = false, 24 | }, 25 | }, 26 | prompt = { 27 | enabled = false, 28 | }, 29 | } 30 | 31 | local function desc(description) 32 | return { noremap = true, silent = true, desc = description } 33 | end 34 | vim.keymap.set({ 'n', 'x', 'o' }, '', flash.jump, desc('flash: jump')) 35 | vim.keymap.set({ 'n', 'x' }, 'r', flash.jump, desc('flash: jump')) 36 | vim.keymap.set({ 'o' }, 'r', flash.remote, desc('flash: remote')) 37 | -------------------------------------------------------------------------------- /nvim/plugin/harpoon.lua: -------------------------------------------------------------------------------- 1 | local keymap = require('lz.n').keymap { 2 | 'harpoon', 3 | keys = 'tm', 4 | after = function() 5 | require('harpoon'):setup { 6 | settings = { 7 | save_on_toggle = true, 8 | }, 9 | } 10 | end, 11 | } 12 | 13 | ---@param keybinding string 14 | ---@param cmd function | string 15 | ---@param description string 16 | local function nnoremap(keybinding, cmd, description) 17 | keymap.set('n', keybinding, cmd, { noremap = true, silent = true, desc = description }) 18 | end 19 | 20 | nnoremap('mm', function() 21 | require('harpoon'):list():append() 22 | end, 'harpoon: [mm]ark') 23 | nnoremap('hm', function() 24 | local harpoon = require('harpoon') 25 | harpoon.ui:toggle_quick_menu(harpoon:list()) 26 | end, '[h]arpoon: toggle [m]enu') 27 | nnoremap('[h', function() 28 | require('harpoon'):list():prev() 29 | end, '[h]arpoon: previous') 30 | nnoremap(']h', function() 31 | require('harpoon'):list():prev() 32 | end, '[h]arpoon: next') 33 | -------------------------------------------------------------------------------- /lib/lua/lang/haskell.lua: -------------------------------------------------------------------------------- 1 | pcall(function() 2 | require('lz.n').trigger_load('telescope.nvim') 3 | require('telescope').load_extension('ht') 4 | end) 5 | 6 | local M = {} 7 | 8 | function M.set_keymaps() 9 | vim.keymap.set('n', 'gp', function() 10 | require('haskell-tools').project.telescope_package_grep() 11 | end, { noremap = true, silent = true, desc = 'haskell: telescope [g]rep [p]ackage' }) 12 | vim.keymap.set('n', 'gf', function() 13 | require('haskell-tools').project.telescope_package_files() 14 | end, { noremap = true, silent = true, desc = 'haskell: telescope packa[g]e [f]ind files' }) 15 | vim.keymap.set('n', 'gy', function() 16 | require('haskell-tools').project.open_package_yaml() 17 | end, { noremap = true, silent = true, desc = 'haskell: [g]o to package.[y]aml' }) 18 | vim.keymap.set('n', 'gc', function() 19 | require('haskell-tools').project.open_package_cabal() 20 | end, { noremap = true, silent = true, desc = 'haskell: [g]o to .[c]abal' }) 21 | end 22 | 23 | return M 24 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/python.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local lsp = require('mrcjk.lsp') 5 | 6 | local pylsp_cmd = 'pylsp' 7 | 8 | if vim.fn.executable(pylsp_cmd) ~= 1 then 9 | return 10 | end 11 | 12 | local config = { 13 | cmd = { pylsp_cmd }, 14 | root_dir = vim.fs.dirname(vim.fs.find({ '.git', 'setup.py', 'setup.cfg', 'pyproject.toml' }, { upward = true })[1]), 15 | capabilities = lsp.capabilities, 16 | filetypes = { 'python' }, 17 | settings = { 18 | pylsp = { 19 | plugins = { 20 | flake8 = { enabled = true }, 21 | pycodestyle = { enabled = false }, 22 | pyflakes = { enabled = false }, 23 | pylint = { enabled = false }, 24 | mccabe = { enabled = false }, 25 | }, 26 | }, 27 | }, 28 | } 29 | 30 | local bufnr = vim.api.nvim_get_current_buf() 31 | 32 | vim.lsp.start(config, { 33 | bufnr = bufnr, 34 | reuse_client = function(client, conf) 35 | return client.name == conf.name and client.config.root_dir == conf.root_dir 36 | end, 37 | }) 38 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/yaml.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local lsp = require('mrcjk.lsp') 5 | 6 | local fname = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(0), ':t') 7 | if fname == 'package.yaml' then 8 | require('lang.haskell').set_keymaps() 9 | end 10 | 11 | if vim.fn.executable('yaml-language-server') ~= 1 then 12 | return 13 | end 14 | 15 | ---@diagnostic disable-next-line: missing-fields 16 | vim.lsp.start { 17 | name = 'yaml-ls', 18 | cmd = { 'yaml-language-server', '--stdio' }, 19 | filetypes = { 'yaml' }, 20 | capabilities = lsp.capabilities, 21 | settings = { 22 | yaml = { 23 | schemaStore = { 24 | -- Disable built-in schemaStore support to use 25 | -- schemastore.nvim and its advanced options like `ignore`. 26 | enable = false, 27 | -- Avoid TypeError: Cannot read properties of undefined (reading 'length') 28 | url = '', 29 | }, 30 | schemas = require('schemastore').yaml.schemas(), 31 | }, 32 | }, 33 | } 34 | 35 | vim.wo[0].spell = false 36 | -------------------------------------------------------------------------------- /.github/workflows/nix-build.yml: -------------------------------------------------------------------------------- 1 | name: "Nix build" 2 | on: 3 | pull_request: 4 | push: 5 | branches: [master] 6 | workflow_call: 7 | jobs: 8 | checks: 9 | name: Checks 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: cachix/install-nix-action@v20 14 | - uses: cachix/cachix-action@v12 15 | with: 16 | name: mrcjkb 17 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 18 | - run: nix flake check -L 19 | build: 20 | name: ${{matrix.attribute }} / ${{ matrix.os }} 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | os: 26 | - ubuntu-latest 27 | attribute: 28 | - .#devShells.x86_64-linux.default 29 | - .#nvim 30 | - .#nvim-dev 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: cachix/install-nix-action@v20 34 | - uses: cachix/cachix-action@v12 35 | with: 36 | name: mrcjkb 37 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 38 | - run: nix build "${{matrix.attribute}}" -L 39 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: update-flake-lock 2 | on: 3 | workflow_dispatch: # allows manual triggering 4 | schedule: 5 | - cron: '0 0 * * 0' # runs weekly on Sunday at 00:00 6 | 7 | jobs: 8 | lockfile: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v2 13 | - name: Install Nix 14 | uses: cachix/install-nix-action@v16 15 | with: 16 | extra_nix_config: | 17 | experimental-features = nix-command flakes 18 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 19 | - name: Update flake.lock 20 | uses: DeterminateSystems/update-flake-lock@v12 21 | with: 22 | token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} 23 | pr-title: "chore: update flake.lock" # Title of PR to be created 24 | pr-labels: | # Labels to be set on the PR 25 | dependencies 26 | automated 27 | - uses: reitermarkus/automerge@v2 28 | with: 29 | token: ${{ secrets.GH_TOKEN_FOR_UPDATES }} 30 | merge-method: squash 31 | pull-request: ${{ github.event.inputs.pull-request }} 32 | -------------------------------------------------------------------------------- /nvim/plugin/comment.lua: -------------------------------------------------------------------------------- 1 | local cfg = { 2 | opleader = { line = 'gc', block = 'gb' }, 3 | toggler = { 4 | line = 'gcc', 5 | block = 'gbc', 6 | }, 7 | } 8 | 9 | vim.keymap.set('n', cfg.opleader.line, '(comment_toggle_linewise)', { desc = 'Comment toggle linewise' }) 10 | vim.keymap.set('n', cfg.opleader.block, '(comment_toggle_blockwise)', { desc = 'Comment toggle blockwise' }) 11 | 12 | vim.keymap.set('n', cfg.toggler.line, function() 13 | return vim.api.nvim_get_vvar('count') == 0 and '(comment_toggle_linewise_current)' 14 | or '(comment_toggle_linewise_count)' 15 | end, { expr = true, desc = 'Comment toggle current line' }) 16 | vim.keymap.set('n', cfg.toggler.block, function() 17 | return vim.api.nvim_get_vvar('count') == 0 and '(comment_toggle_blockwise_current)' 18 | or '(comment_toggle_blockwise_count)' 19 | end, { expr = true, desc = 'Comment toggle current block' }) 20 | 21 | -- VISUAL mode mappings 22 | vim.keymap.set( 23 | 'x', 24 | cfg.opleader.line, 25 | '(comment_toggle_linewise_visual)', 26 | { desc = 'Comment toggle linewise (visual)' } 27 | ) 28 | 29 | vim.keymap.set( 30 | 'x', 31 | cfg.opleader.block, 32 | '(comment_toggle_blockwise_visual)', 33 | { desc = 'Comment toggle blockwise (visual)' } 34 | ) 35 | -------------------------------------------------------------------------------- /nvim/queries/haskell/injections.scm: -------------------------------------------------------------------------------- 1 | ;; extends 2 | 3 | ; quasiquoted sql 4 | ;; (quasiquote 5 | ;; (quoter) @_name 6 | ;; (#eq? @_name "sql") 7 | ;; ((quasiquote_body) @sql) 8 | ;; ) 9 | ; quasiquoted sql (any quasiqutoe) 10 | ; (exp_apply 11 | ; (exp_name 12 | ; (qualified_variable 13 | ; (((variable) @_func_name)(#any-of? @_func_name "query" "query_" "queryWith" "queryWith_" "execute" "execute_" "executeMany")) 14 | ; ) 15 | ; ) 16 | ; (exp_name) ;; the Connecton 17 | ; (quasiquote 18 | ; ((quasiquote_body) @sql) 19 | ; ) 20 | ; ) 21 | ; (exp_apply 22 | ; (exp_name 23 | ; (((variable) @_func_name)(#any-of? @_func_name "query" "query_" "queryWith" "queryWith_" "execute" "execute_" "executeMany")) 24 | ; ) 25 | ; (exp_name) ;; the Connection 26 | ; (quasiquote 27 | ; ((quasiquote_body) @sql) 28 | ; ) 29 | ; ) 30 | ; Qualified function that returns a Query 31 | ; ( 32 | ; (signature 33 | ; (_ 34 | ; ((qualified_type (type) @type_name)(#eq? @type_name "Query")) 35 | ; ) 36 | ; ) 37 | ; (function 38 | ; (_ 39 | ; (quasiquote_body) @sql 40 | ; ) 41 | ; ) 42 | ; ) 43 | 44 | ; Unqualified function that returns a Query 45 | ( 46 | (signature 47 | (_ 48 | ((type) @type_name)(#eq? @type_name "Query") 49 | ) 50 | ) 51 | (function 52 | (_ 53 | (quasiquote_body) @sql 54 | ) 55 | ) 56 | ) 57 | 58 | -------------------------------------------------------------------------------- /nvim/plugin/persistence.lua: -------------------------------------------------------------------------------- 1 | local is_setup = false 2 | local is_active = false 3 | 4 | require('persistence.config').setup() 5 | 6 | vim.api.nvim_create_autocmd('FileType', { 7 | group = vim.api.nvim_create_augroup('persistence', { clear = true }), 8 | once = true, 9 | pattern = '*', 10 | callback = function() 11 | if is_setup then 12 | return 13 | end 14 | local p = require('persistence') 15 | p.setup() 16 | 17 | vim.keymap.set('n', 'ss', function() 18 | p.save() 19 | vim.notify('Saved session.', vim.log.levels.INFO) 20 | end, { desc = '[s]ession: [s]ave' }) 21 | 22 | vim.keymap.set('n', 'sx', function() 23 | if is_active then 24 | p.stop() 25 | is_active = false 26 | vim.notify('Stopped session recording.', vim.log.levels.INFO) 27 | else 28 | p.start() 29 | is_active = true 30 | vim.notify('Started session recording.', vim.log.levels.INFO) 31 | end 32 | end, { desc = 'start/stop recording [s]ession [x]' }) 33 | 34 | is_setup = true 35 | end, 36 | }) 37 | 38 | vim.keymap.set('n', 'sl', function() 39 | require('persistence').load() 40 | vim.notify('Loaded session.', vim.log.levels.INFO) 41 | end, { desc = '[s]ession: [l]oad' }) 42 | 43 | vim.api.nvim_create_user_command('S', function() 44 | require('persistence').load() 45 | end, {}) 46 | -------------------------------------------------------------------------------- /nvim/queries/nix/injections.scm: -------------------------------------------------------------------------------- 1 | ;; extends 2 | 3 | (apply_expression 4 | function: (_ 5 | function: (_ 6 | function: (_) @_func 7 | (#match? @_func "(^|\\.)writeShellScriptBinWith$") 8 | ) 9 | ) 10 | argument: [ 11 | (string_expression (string_fragment) @injection.content (#set! injection.language "nix")) 12 | (indented_string_expression (string_fragment) @injection.content (#set! injection.language "nix")) 13 | ] (#set! injection.combined)) 14 | 15 | 16 | ;; The below queries will be upstreamed 17 | 18 | ;; (nixosTest) testScript 19 | ((binding 20 | attrpath: (attrpath) @_attr_name 21 | (#eq? @_attr_name "nodes") 22 | ) 23 | (binding 24 | attrpath: (attrpath) @_func_name (#eq? @_func_name "testScript") 25 | (_ 26 | (string_fragment) @injection.content (#set! injection.language "python") 27 | )) 28 | (#set! injection.combined)) 29 | 30 | ;; home-manager Neovim plugin config 31 | (attrset_expression 32 | (binding_set 33 | (binding 34 | attrpath: (attrpath) @_ty_attr 35 | (_ 36 | (string_fragment) @_ty 37 | ) 38 | (#eq? @_ty_attr "type") 39 | (#eq? @_ty "lua")) 40 | (binding 41 | 42 | attrpath: (attrpath) @_cfg_attr 43 | (_ 44 | (string_fragment) @injection.content (#set! injection.language "lua") 45 | ) 46 | (#eq? @_cfg_attr "config") 47 | )) 48 | (#set! injection.combined)) 49 | -------------------------------------------------------------------------------- /nvim/plugin/terminal.lua: -------------------------------------------------------------------------------- 1 | ---@class termopen.Opts 2 | ---@field insert? boolean 3 | ---@field disable_line_numbers? boolean 4 | 5 | ---@param opts? termopen.Opts 6 | local function extend_default_opts(opts) 7 | opts = opts or {} 8 | opts.insert = opts.insert == nil and true or opts.insert 9 | return opts 10 | end 11 | 12 | ---@param cmd string 13 | ---@param opts? termopen.Opts 14 | local function termopen(cmd, opts) 15 | opts = extend_default_opts(opts) 16 | vim.cmd.tabnew() 17 | local buf = vim.api.nvim_get_current_buf() 18 | vim.fn.jobstart(cmd, { 19 | on_exit = function() 20 | vim.api.nvim_buf_delete(buf, {}) 21 | end, 22 | term = true, 23 | }) 24 | if opts.insert then 25 | vim.cmd.startinsert() 26 | end 27 | if opts.disable_line_numbers then 28 | vim.opt.number = false 29 | vim.opt.relativenumber = false 30 | end 31 | end 32 | 33 | vim.keymap.set('n', 'go', function() 34 | termopen('gitu') 35 | end, { noremap = true, silent = true, desc = '[g]itu: [o]pen' }) 36 | 37 | vim.keymap.set('n', 'jj', function() 38 | termopen('lazyjj', { 39 | disable_line_numbers = true, 40 | }) 41 | end, { noremap = true, silent = true, desc = 'lazy[j][j]' }) 42 | 43 | vim.api.nvim_create_autocmd('TermOpen', { 44 | callback = function() 45 | vim.opt.relativenumber = true 46 | end, 47 | group = vim.api.nvim_create_augroup('terminal-set-relative-number', { clear = true }), 48 | }) 49 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/nix.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local lsp = require('mrcjk.lsp') 5 | 6 | local bufnr = vim.api.nvim_get_current_buf() 7 | 8 | if vim.fn.executable('pre-commit') == 1 then 9 | vim.keymap.set('n', 'pf', function() 10 | vim.system({ 'pre-commit', 'run', '--file', vim.api.nvim_buf_get_name(bufnr), 'alejandra' }, nil, function() 11 | vim.schedule(function() 12 | vim.cmd.checktime() 13 | end) 14 | end) 15 | end, { noremap = true, silent = true, buffer = bufnr, desc = 'pre-commit run alejandra' }) 16 | end 17 | 18 | if vim.fn.executable('nixd') == 1 then 19 | vim.lsp.start { 20 | name = 'nixd', 21 | cmd = { 'nixd' }, 22 | root_dir = vim.fs.dirname(vim.fs.find({ 'flake.nix', '.git' }, { upward = true })[1]), 23 | capabilities = lsp.capabilities, 24 | } 25 | elseif vim.fn.executable('nil') == 1 then 26 | ---@diagnostic disable-next-line: missing-fields 27 | vim.lsp.start { 28 | name = 'nil_ls', 29 | cmd = { 'nil' }, 30 | root_dir = vim.fs.dirname(vim.fs.find({ 'flake.nix', '.git' }, { upward = true })[1]), 31 | capabilities = lsp.capabilities, 32 | filetypes = { 'nix' }, 33 | settings = { 34 | ['nil'] = { 35 | formatting = { 36 | command = vim.fn.executable('alejandra') == 1 and { 'alejandra', '-qq' } 37 | or vim.fn.executable('nixpkgs-fmt') == 1 and { 'nixpkgs-fmt' } 38 | or nil, 39 | }, 40 | flake = { 41 | autoArchive = true, 42 | autoEvalInputs = true, 43 | }, 44 | }, 45 | }, 46 | } 47 | end 48 | 49 | vim.api.nvim_set_hl(0, '@lsp.type.property.nix', {}) 50 | vim.api.nvim_set_hl(0, '@lsp.type.variable.nix', {}) 51 | -------------------------------------------------------------------------------- /lib/lua/lang/texlab.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local on_latex_attach = function() 4 | vim.keymap.set('n', 'lb', function() 5 | vim.cmd.te { 'pdflatex', '-file-line-error', '-halt-on-error', '%' } 6 | end, { noremap = true, silent = true, desc = '[l]atex [b]uild' }) 7 | end 8 | 9 | local root_files = { '.git', '.latexmkrc', '.texlabroot', 'texlabroot', 'Tectonic.toml' } 10 | 11 | M.launch = function() 12 | if vim.fn.executable('texlab') == 1 then 13 | vim.lsp.start { 14 | cmd = { 'texlab' }, 15 | filetypes = { 'tex', 'plaintex', 'bib', 'texlab' }, 16 | root_dir = vim.fs.dirname(vim.fs.find(root_files, { upward = true })[1]), 17 | on_attach = on_latex_attach, 18 | capabilities = require('mrcjk.lsp').capabilities, 19 | single_file_support = true, 20 | settings = { 21 | texlab = { 22 | rootDirectory = nil, 23 | build = { 24 | executable = 'pdflatex', 25 | args = { '-file-line-error', '-halt-on-error', '%f' }, 26 | onSave = true, 27 | forwardSearchAfter = false, 28 | }, 29 | auxDirectory = '.', 30 | forwardSearch = { 31 | executable = nil, 32 | args = {}, 33 | }, 34 | chktex = { 35 | onOpenAndSave = false, 36 | onEdit = false, 37 | }, 38 | diagnosticsDelay = 300, 39 | latexFormatter = 'latexindent', 40 | latexindent = { 41 | ['local'] = nil, -- local is a reserved keyword 42 | modifyLineBreaks = false, 43 | }, 44 | bibtexFormatter = 'texlab', 45 | formatterLineLength = 80, 46 | }, 47 | }, 48 | } 49 | end 50 | end 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /nvim/queries/haskell/highlights.scm: -------------------------------------------------------------------------------- 1 | ;; extends 2 | 3 | ; NOTE: lsp semantic tokens have priority 125 4 | 5 | (quasiquote 6 | (quoter 7 | (_ 8 | (module (module_id) @_mod) 9 | . (variable) @_name)) 10 | (#eq? @_mod "Log") 11 | (#any-of? @_name "t" "d" "i" "w" "e") 12 | (quasiquote_body) @string) 13 | 14 | (quasiquote 15 | (quoter 16 | (_ 17 | (module (module_id) @_mod) 18 | . (variable) @keyword.exception)) 19 | (#eq? @_mod "Log") 20 | (#eq? @keyword.exception "e")) 21 | 22 | ((variable) @keyword.exception 23 | (#any-of? @keyword.exception 24 | "throwString" 25 | "throwWhenNothing" 26 | "failWhenNothing" 27 | "failWhenLeft_" 28 | ) 29 | (#set! "priority" 200)) 30 | 31 | ((variable) @keyword.exception 32 | (#any-of? @keyword.exception 33 | "throwString" 34 | "throwWhenNothing" 35 | "failWhenNothing" 36 | "failWhenLeft_" 37 | ) 38 | (#set! "priority" 200)) 39 | 40 | ; Blocking STM calls "@keyword.blocking" is a non-standard capture 41 | ((variable) @keyword.blocking 42 | (#any-of? @keyword.blocking 43 | "readMVar" 44 | "putMVar" 45 | "takeMVar" 46 | "putTMVar" 47 | "putTMVarIO" 48 | "takeTMVar" 49 | "takeTMVarIO" 50 | "readTMVar" 51 | "readTMVarIO" 52 | "swapTMVar" 53 | "swapTMVarIO" 54 | "swapTMVar_" 55 | "swapTMVarIO_" 56 | "readTBQueue" 57 | "readTBQueueIO" 58 | "peekTBQueue" 59 | "peekTBQueueIO" 60 | "writeTBQueue" 61 | "writeTBQueueIO" 62 | "unGetTBQueue" 63 | "unGetTBQueueIO" 64 | "readTQueue" 65 | "readTQueueIO" 66 | "peekTQueue" 67 | "peekTQueueIO" 68 | "unGetTQueue" 69 | "unGetTQueueIO" 70 | "readTChan" 71 | "readTChanIO" 72 | "peekTChan" 73 | "peekTChanIO" 74 | "unGetTChan" 75 | "unGetTChanIO" 76 | "waitTSem" 77 | "waitTSemIO" 78 | ) 79 | (#set! "priority" 200)) 80 | -------------------------------------------------------------------------------- /lib/lua/mrcjk/neotest.lua: -------------------------------------------------------------------------------- 1 | ---Neotest is quite heavy on startup, so this module is loaded in ftplugins 2 | if vim.g.neotest_setup_done then 3 | return true 4 | end 5 | vim.g.neotest_setup_done = true 6 | 7 | vim.cmd.packadd('neotest') 8 | vim.cmd.packadd('neotest-java') 9 | 10 | local function desc(description) 11 | return { noremap = true, silent = true, desc = description } 12 | end 13 | 14 | local neotest = require('neotest') 15 | 16 | ---@diagnostic disable-next-line: missing-fields 17 | neotest.setup { 18 | adapters = { 19 | require('neotest-haskell') { 20 | frameworks = { 21 | { 22 | framework = 'tasty', 23 | modules = { 'Test.Tasty', 'Tiko' }, 24 | }, 25 | 'hspec', 26 | 'sydtest', 27 | }, 28 | }, 29 | require('rustaceanvim.neotest'), 30 | require('neotest-java'), 31 | require('neotest-busted'), 32 | }, 33 | ---@diagnostic disable-next-line: missing-fields 34 | discovery = { 35 | enabled = true, 36 | }, 37 | icons = { 38 | failed = '', 39 | passed = '', 40 | running = '', 41 | skipped = '', 42 | unknown = '', 43 | }, 44 | quickfix = { 45 | enabled = false, 46 | open = false, 47 | }, 48 | } 49 | vim.keymap.set('n', 'nr', neotest.run.run, desc('[n]eotest: [r]un nearest')) 50 | vim.keymap.set('n', 'nf', function() 51 | neotest.run.run(vim.api.nvim_buf_get_name(0)) 52 | end, desc('[n]eotest: run [f]ile')) 53 | vim.keymap.set('n', 'nw', function() 54 | neotest.watch.toggle(vim.api.nvim_buf_get_name(0)) 55 | end, desc('[n]eotest: [w]atch file')) 56 | vim.keymap.set('n', 'no', neotest.output.open, desc('[n]eotest: [o]pen output')) 57 | vim.keymap.set('n', 'np', neotest.output_panel.toggle, desc('[n]eotest: output-[p]anel')) 58 | vim.keymap.set('n', 'ns', neotest.summary.toggle, desc('[n]eotest: toggle [s]ummary')) 59 | 60 | return true 61 | -------------------------------------------------------------------------------- /nvim/plugin/gitlinker.lua: -------------------------------------------------------------------------------- 1 | if vim.g.gitlinker_setup_done then 2 | return 3 | end 4 | vim.api.nvim_create_autocmd('FileType', { 5 | once = true, 6 | pattern = '*', 7 | group = vim.api.nvim_create_augroup('gitlinker-setup', {}), 8 | callback = function() 9 | if vim.g.gitlinker_setup_done then 10 | return 11 | end 12 | vim.g.gitlinker_setup_done = true 13 | local gitlinker = require('gitlinker') 14 | 15 | ---@param dest string 16 | local function mk_gitlab_internal_router(dest) 17 | return 'https://gitlab.internal.tiko.ch/' 18 | .. '{_A.ORG}/' 19 | .. '{_A.REPO}/blob/' 20 | .. dest 21 | .. '{_A.FILE}' 22 | .. '#L{_A.LSTART}' 23 | .. "{(_A.LEND > _A.LSTART and ('-L' .. _A.LEND) or '')}" 24 | end 25 | 26 | gitlinker.setup { 27 | router = { 28 | browse = { 29 | ['^gitlab.internal.tiko%.ch'] = mk_gitlab_internal_router('{_A.REV}/'), 30 | }, 31 | blame = { 32 | ['^gitlab.internal.tiko%.ch'] = mk_gitlab_internal_router('{_A.REPO}/blame/{_A.REV}/'), 33 | }, 34 | default_branch = { 35 | ['^gitlab.internal.tiko%.ch'] = mk_gitlab_internal_router('{_A.DEFAULT_BRANCH}/'), 36 | }, 37 | current_branch = { 38 | ['^gitlab.internal.tiko%.ch'] = mk_gitlab_internal_router('{_A.CURRENT_BRANCH}/'), 39 | }, 40 | }, 41 | } 42 | 43 | vim.keymap.set({ 'n', 'v' }, 'gbb', function() 44 | vim.cmd.GitLink { bang = true } 45 | end, { silent = true, desc = '[g]it: open in [bb]rowser' }) 46 | 47 | vim.keymap.set('n', 'gBc', function() 48 | vim.cmd.GitLink { 'current_branch', bang = true } 49 | end, { silent = true, desc = '[g]it: open in [B]rowser ([c]urrent branch)' }) 50 | 51 | vim.keymap.set('n', 'gBd', function() 52 | vim.cmd.GitLink { 'default_branch', bang = true } 53 | end, { silent = true, desc = '[g]it: open in [B]rowser ([d]efault branch)' }) 54 | end, 55 | }) 56 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/lua.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | require('mrcjk.neotest') 5 | local bufnr = vim.api.nvim_get_current_buf() 6 | 7 | vim.bo[bufnr].comments = ':---,:--' 8 | 9 | if vim.fn.executable('pre-commit') == 1 then 10 | vim.keymap.set('n', 'pf', function() 11 | vim.system({ 'pre-commit', 'run', '--file', vim.api.nvim_buf_get_name(bufnr), 'stylua' }, nil, function() 12 | vim.schedule(function() 13 | vim.cmd.checktime() 14 | end) 15 | end) 16 | end, { noremap = true, silent = true, buffer = bufnr, desc = 'pre-commit run stylua' }) 17 | end 18 | 19 | local lsp = require('mrcjk.lsp') 20 | 21 | local root_files = { 22 | '.git', 23 | '.luarc.json', 24 | '.luarc.jsonc', 25 | '.luacheckrc', 26 | '.stylua.toml', 27 | 'stylua.toml', 28 | 'selene.toml', 29 | 'selene.yml', 30 | } 31 | 32 | local emmylua_ls_cmd = 'emmylua_ls' 33 | local lua_ls_cmd = 'lua-language-server' 34 | 35 | if vim.fn.executable(emmylua_ls_cmd) == 1 then 36 | ---@diagnostic disable-next-line: missing-fields 37 | vim.lsp.start { 38 | name = 'emmylua-ls', 39 | cmd = { emmylua_ls_cmd }, 40 | workspace_required = false, 41 | } 42 | elseif vim.fn.executable(lua_ls_cmd) == 1 then 43 | ---@diagnostic disable-next-line: missing-fields 44 | vim.lsp.start { 45 | name = 'luals', 46 | cmd = { lua_ls_cmd }, 47 | root_dir = vim.fs.dirname(vim.fs.find(root_files, { upward = true })[1]), 48 | filetypes = { 'lua' }, 49 | capabilities = lsp.capabilities, 50 | settings = { 51 | Lua = { 52 | runtime = { 53 | version = 'LuaJIT', 54 | }, 55 | diagnostics = { 56 | -- Get the language server to recognize the `vim` global, etc. 57 | globals = { 58 | 'vim', 59 | 'describe', 60 | 'it', 61 | 'assert', 62 | 'stub', 63 | }, 64 | disable = { 65 | 'duplicate-set-field', 66 | }, 67 | }, 68 | workspace = { 69 | checkThirdParty = false, 70 | library = { 71 | '${3rd}/busted/library', 72 | '${3rd}/luassert/library', 73 | }, 74 | }, 75 | telemetry = { 76 | enable = false, 77 | }, 78 | hint = { -- inlay hints 79 | enable = true, 80 | }, 81 | }, 82 | }, 83 | } 84 | end 85 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/toml.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local keymap = vim.keymap 5 | 6 | local bufnr = vim.api.nvim_get_current_buf() 7 | local buf_filename = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(bufnr), ':t') 8 | 9 | if not vim.g.did_crates_nvim_init and buf_filename == 'Cargo.toml' then 10 | local function code_action() 11 | return require('actions-preview').code_actions() 12 | -- return vim.lsp.buf.code_action() 13 | end 14 | 15 | require('crates').setup { 16 | lsp = { 17 | enabled = true, 18 | on_attach = function(_, buf) 19 | local function desc(description) 20 | return { noremap = true, silent = true, buffer = buf, desc = description } 21 | end 22 | keymap.set('n', '', code_action, desc('lsp: code action')) 23 | keymap.set('n', 'K', function() 24 | vim.cmd.Crates('show_popup') 25 | end, desc('crates: popup')) 26 | keymap.set('n', 'cd', function() 27 | vim.cmd.Crates('show_dependencies_popup') 28 | end, desc('[c]rates: [d]ependencies')) 29 | keymap.set('n', 'cf', function() 30 | vim.cmd.Crates('show_features_popup') 31 | end, desc('[c]rates: [f]eatures')) 32 | keymap.set('n', 'cua', function() 33 | vim.cmd.Crates('update_crates') 34 | end, desc('[c]rates: [u]pdate [a]ll')) 35 | keymap.set('n', 'cuc', function() 36 | vim.cmd.Crates('update_crate') 37 | end, desc('[c]rates: [u]pdate [c]rate')) 38 | keymap.set('n', 'coc', function() 39 | vim.cmd.Crates('open_cratesio') 40 | end, desc('[c]rates: [o]open [c]rates.io')) 41 | keymap.set('n', 'cod', function() 42 | vim.cmd.Crates('open_documentation') 43 | end, desc('[c]rates: [o]pen [d]ocumentaion')) 44 | keymap.set('n', 'cor', function() 45 | vim.cmd.Crates('open_repository') 46 | end, desc('[c]rates: [o]pen [r]epository')) 47 | end, 48 | actions = true, 49 | completion = true, 50 | }, 51 | } 52 | vim.g.did_crates_nvim_init = true 53 | end 54 | 55 | local lsp = require('mrcjk.lsp') 56 | 57 | if vim.fn.executable('taplo') ~= 1 then 58 | return 59 | end 60 | 61 | vim.lsp.start { 62 | name = 'taplo', 63 | cmd = { 'taplo', 'lsp', 'stdio' }, 64 | capabilities = lsp.capabilities, 65 | filetypes = { 'toml' }, 66 | init_options = { 67 | configurationSection = 'evenBetterToml', 68 | cachePath = vim.NIL, 69 | }, 70 | root_dir = vim.fs.dirname(vim.fs.find({ 'taplo.toml', '.taplo.toml' }, { upward = true })[1]), 71 | } 72 | -------------------------------------------------------------------------------- /nvim/plugin/qf.lua: -------------------------------------------------------------------------------- 1 | if vim.g.qf_loaded then 2 | return 3 | end 4 | vim.g.qf_loaded = true 5 | 6 | local fn = vim.fn 7 | 8 | function _G.qftf(info) 9 | local items 10 | local ret = {} 11 | -- The name of item in list is based on the directory of quickfix window. 12 | -- Change the directory for quickfix window make the name of item shorter. 13 | -- It's a good opportunity to change current directory in quickfixtextfunc :) 14 | -- 15 | -- local alterBufnr = fn.bufname('#') -- alternative buffer is the buffer before enter qf window 16 | -- local root = getRootByAlterBufnr(alterBufnr) 17 | -- vim.cmd(('noa lcd %s'):format(fn.fnameescape(root))) 18 | -- 19 | if info.quickfix == 1 then 20 | items = fn.getqflist({ id = info.id, items = 0 }).items 21 | else 22 | items = fn.getloclist(info.winid, { id = info.id, items = 0 }).items 23 | end 24 | local limit = 31 25 | local fnameFmt1, fnameFmt2 = '%-' .. limit .. 's', '…%.' .. (limit - 1) .. 's' 26 | local validFmt = '%s │%5d:%-3d│%s %s' 27 | for i = info.start_idx, info.end_idx do 28 | local e = items[i] 29 | local fname = '' 30 | local str 31 | if e.valid == 1 then 32 | if e.bufnr > 0 then 33 | fname = fn.bufname(e.bufnr) or '' 34 | if fname == '' then 35 | fname = '[No Name]' 36 | else 37 | fname = fname:gsub('^' .. vim.env.HOME, '~') 38 | end 39 | -- char in fname may occur more than 1 width, ignore this issue in order to keep performance 40 | if #fname <= limit then 41 | fname = fnameFmt1:format(fname) 42 | else 43 | fname = fnameFmt2:format(fname:sub(1 - limit)) 44 | end 45 | end 46 | local lnum = e.lnum > 99999 and -1 or e.lnum 47 | local col = e.col > 999 and -1 or e.col 48 | local qtype = e.type == '' and '' or ' ' .. e.type:sub(1, 1):upper() 49 | str = validFmt:format(fname, lnum, col, qtype, e.text) 50 | else 51 | str = e.text 52 | end 53 | table.insert(ret, str) 54 | end 55 | return ret 56 | end 57 | 58 | vim.o.qftf = '{info -> v:lua._G.qftf(info)}' 59 | 60 | require('bqf').setup { 61 | filter = { 62 | ---@diagnostic disable-next-line: missing-fields 63 | fzf = { 64 | -- Adapt fzf's delimiter 65 | extra_opts = { '--bind', 'ctrl-o:toggle-all', '--delimiter', '│' }, 66 | }, 67 | }, 68 | ---@diagnostic disable-next-line: missing-fields 69 | preview = { 70 | border = 'none', 71 | win_vheight = 999, 72 | win_height = 999, 73 | winblend = 0, 74 | }, 75 | } 76 | 77 | require('quicker').setup { 78 | opts = { 79 | relativenumber = true, 80 | -- keys = { 81 | -- { '', "lua require('quicker').expand()", desc = 'Expand quickfix content' }, 82 | -- }, 83 | highlight = { 84 | load_buffers = false, 85 | }, 86 | }, 87 | } 88 | 89 | vim.keymap.set('n', '', function() 90 | require('quicker').toggle() 91 | end, { desc = 'toggle quickfix list' }) 92 | -------------------------------------------------------------------------------- /nvim/plugin/gitsigns.lua: -------------------------------------------------------------------------------- 1 | if vim.g.gitsigns_nvim_setup_done then 2 | return 3 | end 4 | vim.api.nvim_create_autocmd('FileType', { 5 | once = true, 6 | pattern = '*', 7 | group = vim.api.nvim_create_augroup('gitsigns-nvim-setup', {}), 8 | callback = function() 9 | if vim.g.gitsigns_nvim_setup_done then 10 | return 11 | end 12 | vim.g.gitsigns_nvim_setup_done = true 13 | vim.schedule(function() 14 | require('gitsigns').setup { 15 | current_line_blame = true, 16 | current_line_blame_opts = { 17 | ignore_whitespace = true, 18 | }, 19 | on_attach = function(bufnr) 20 | local gs = package.loaded.gitsigns 21 | 22 | local function map(mode, l, r, opts) 23 | opts = opts or {} 24 | opts.buffer = bufnr 25 | vim.keymap.set(mode, l, r, opts) 26 | end 27 | 28 | -- Navigation 29 | map('n', ']g', function() 30 | if vim.wo.diff then 31 | return ']g' 32 | end 33 | vim.schedule(function() 34 | gs.next_hunk() 35 | end) 36 | return '' 37 | end, { expr = true, desc = '[g]it: next hunk' }) 38 | 39 | map('n', '[g', function() 40 | if vim.wo.diff then 41 | return '[g' 42 | end 43 | vim.schedule(function() 44 | gs.prev_hunk() 45 | end) 46 | return '' 47 | end, { expr = true, desc = '[g]it: previous hunk' }) 48 | 49 | -- Actions 50 | map({ 'n', 'v' }, 'gh', function() 51 | vim.cmd.Gitsigns('stage_hunk') 52 | end, { desc = '[g]it: stage [h]unk' }) 53 | map({ 'n', 'v' }, '[g]r', function() 54 | vim.cmd.Gitsigns('reset_hunk') 55 | end, { desc = '[g]it: [r]eset hunk' }) 56 | map('n', 'g[S]', gs.stage_buffer, { desc = '[g]it: [S]tage buffer' }) 57 | map('n', 'gu', gs.undo_stage_hunk, { desc = '[g]it: [u]ndo stage hunk' }) 58 | map('n', 'gR', gs.reset_buffer, { desc = '[g]it: [R]eset buffer' }) 59 | map('n', 'gp', gs.preview_hunk, { desc = '[g]it: [p]review hunk' }) 60 | map('n', 'gL', function() 61 | gs.blame_line { full = true } 62 | end, { desc = '[g]it: blame [L]ine (full)' }) 63 | map('n', 'glb', gs.toggle_current_line_blame, { desc = '[g]it: toggle [l]ine [b]lame' }) 64 | map('n', 'gDt', gs.diffthis, { desc = '[g]it: [D]iff [t]his' }) 65 | map('n', 'gDD', function() 66 | gs.diffthis('~') 67 | end, { desc = '[g]it: [DD]iff ~' }) 68 | map('n', 'gtd', gs.toggle_deleted, { desc = '[g]it: [t]oggle [d]eleted' }) 69 | -- Text object 70 | map({ 'o', 'x' }, 'ih', ':Gitsigns select_hunk', { desc = 'git: [i]nner [h]unk' }) 71 | end, 72 | } 73 | end) 74 | end, 75 | }) 76 | -------------------------------------------------------------------------------- /nvim/spell/en.utf-8.add: -------------------------------------------------------------------------------- 1 | foldlevelstart 2 | foldmethod 3 | undos 4 | dbox 5 | v3 6 | timeseries 7 | protobuf 8 | DeviceState 9 | #umder 10 | proto 11 | Avro 12 | avro 13 | CustomerGraphMeasurement 14 | DboxV3History 15 | onAvailable 16 | onMissing 17 | withClientMay 18 | Redis 19 | UUID 20 | mkTemplates 21 | VPP 22 | Hoogle 23 | url 24 | LSP 25 | config 26 | Repl 27 | codelens 28 | lsp 29 | Neovim 30 | typeDefinition 31 | neovim 32 | repl 33 | rockspec 34 | luarocks 35 | ftplugin 36 | haskell 37 | lua 38 | codeLens 39 | LuaRocks 40 | #orflow 41 | JSON 42 | configs 43 | hoogle 44 | Vimdocs 45 | toggleterm 46 | retopologization 47 | Retopolization 48 | VANs 49 | ExceptT 50 | PluginRuleFailed 51 | MaybeT 52 | nvim 53 | scrolloff 54 | lualine 55 | statusline 56 | pkgs 57 | ghc 58 | nixpkgs 59 | legacyPackages 60 | devShells 61 | ufo 62 | param 63 | yaml 64 | DAP 65 | bufnr 66 | json 67 | GHCi 68 | hls 69 | keymaps 70 | dap 71 | Neovim's 72 | treesitter 73 | filetype 74 | rsync 75 | perl 76 | redis 77 | Struct 78 | pipelining 79 | SPDX 80 | github 81 | specrev 82 | args 83 | webhook 84 | tmp 85 | cmd 86 | repo's 87 | rockspec's 88 | sha 89 | luacheck 90 | stdout 91 | stderr 92 | TVar 93 | serializable 94 | deserialise 95 | DEH 96 | BINs 97 | cargoDeps 98 | gui 99 | src 100 | ADT 101 | GADT 102 | quasiquote 103 | scm 104 | enum 105 | buf 106 | keymap 107 | luasnip 108 | direnv 109 | python39Packages 110 | python310Packages 111 | python311Packages 112 | init 113 | gitlab 114 | tiko 115 | destructured 116 | editorconfig 117 | filepath 118 | tbl 119 | main 120 | import 121 | func 122 | nixos 123 | rustPlatform 124 | NixOS 125 | preproc 126 | rhs 127 | lhs 128 | MVar 129 | namespaced 130 | utf8 131 | p12 132 | RecordWildCards 133 | Vimscript 134 | dotfiles 135 | shouldn 136 | keymap 137 | cfdo 138 | quickfix 139 | undofile 140 | sqlite 141 | textobject 142 | charwise 143 | linewise 144 | blockwise 145 | pictograms 146 | vscode 147 | stylua 148 | autocommand 149 | toml 150 | CPATH 151 | cpath 152 | protonmail 153 | GPLv3 154 | #sync 155 | filesystem 156 | kickstart 157 | wait 158 | autocommands 159 | Attrset 160 | utf 161 | charset 162 | lf 163 | whitespace 164 | stderr 165 | switchabilities 166 | foldr 167 | vpp 168 | toml's 169 | ferris 170 | graphviz 171 | textDocument 172 | toolchain 173 | Word8 174 | attrs 175 | msg 176 | cfg 177 | markdownlint 178 | pragma 179 | dylib 180 | jooq 181 | devShell 182 | Malhotra 183 | Etzbergstrasse 184 | derating 185 | aFRR 186 | FCR 187 | Hazelcast 188 | smartiesbackend 189 | gsub 190 | metatable 191 | NodeJS 192 | runtimepath 193 | colorschemes 194 | Noogle 195 | filetypes 196 | autoload 197 | semver 198 | DetachedFiles 199 | FIXME 200 | SurroundSCM 201 | Deserialize 202 | Rockspecs 203 | rockspecs 204 | runnables 205 | testables 206 | rustaceanvim 207 | sanitize_test_option/! 208 | wildcard 209 | debuggable 210 | struct 211 | HashMap 212 | get_clients/! 213 | stringly 214 | async/! 215 | Memcached 216 | STM 217 | attrset 218 | checkhealth 219 | checkPhase 220 | installPhase 221 | cwd 222 | nixosTest 223 | QE 224 | handle_configured_options/! 225 | Upsert 226 | -------------------------------------------------------------------------------- /nix/plugin-overlay.nix: -------------------------------------------------------------------------------- 1 | {inputs}: final: prev: let 2 | system = final.stdenv.hostPlatform.system; 3 | mkNvimPlugin = src: pname: 4 | prev.pkgs.vimUtils.buildVimPlugin { 5 | inherit pname src; 6 | version = src.lastModifiedDate; 7 | # nvimRequireCheck hooks fail because this overlay 8 | # ignores dependencies. 9 | doCheck = false; 10 | }; 11 | in { 12 | nvimPlugins = { 13 | plenary = mkNvimPlugin inputs.plenary "plenary.nvim"; 14 | sqlite = mkNvimPlugin inputs.sqlite "sqlite.nvim"; 15 | nvim-web-devicons = mkNvimPlugin inputs.nvim-web-devicons "nvim-web-devicons"; 16 | vim-wordmotion = mkNvimPlugin inputs.vim-wordmotion "vim-wordmotion"; 17 | nvim-highlight-colors = mkNvimPlugin inputs.nvim-highlight-colors "nvim-highlight-colors"; 18 | flash-nvim = mkNvimPlugin inputs.flash-nvim "flash.nvim"; 19 | eyeliner-nvim = mkNvimPlugin inputs.eyeliner-nvim "eyeliner.nvim"; 20 | gitlinker = mkNvimPlugin inputs.gitlinker "gitlinker.nvim"; 21 | surround = mkNvimPlugin inputs.surround "nvim-surround"; 22 | substitute = mkNvimPlugin inputs.substitute "substitute.nvim"; 23 | persistence = mkNvimPlugin inputs.persistence "persistence.nvim"; 24 | nvim-lastplace = mkNvimPlugin inputs.nvim-lastplace "nvim-lastplace"; 25 | comment = mkNvimPlugin inputs.comment "comment.nvim"; 26 | crates-nvim = mkNvimPlugin inputs.crates-nvim "crates-nvim"; 27 | neotest = mkNvimPlugin inputs.neotest "neotest"; 28 | nio = mkNvimPlugin inputs.nio "nvim-nio"; 29 | neotest-java = mkNvimPlugin inputs.neotest-java "neotest-java"; 30 | neotest-busted = mkNvimPlugin inputs.neotest-busted "neotest-busted"; 31 | schemastore-nvim = mkNvimPlugin inputs.schemastore-nvim "SchemaStore.nvim"; 32 | jdtls = mkNvimPlugin inputs.jdtls "nvim-jdtls"; 33 | live-rename-nvim = mkNvimPlugin inputs.live-rename-nvim "live-rename.nvim"; 34 | fidget = mkNvimPlugin inputs.fidget "fidget.nvim"; 35 | illuminate = mkNvimPlugin inputs.illuminate "vim-illuminate"; 36 | actions-preview-nvim = mkNvimPlugin inputs.actions-preview-nvim "actions-preview.nvim"; 37 | treesitter-textobjects = mkNvimPlugin inputs.treesitter-textobjects "treesitter-textobjects"; 38 | treesitter-context = mkNvimPlugin inputs.treesitter-context "treesitter-context"; 39 | nvim-ts-context-commentstring = mkNvimPlugin inputs.nvim-ts-context-commentstring "nvim-ts-context-commentstring"; 40 | rainbow-delimiters-nvim = mkNvimPlugin inputs.rainbow-delimiters-nvim "rainbow-delimiters.nvim"; 41 | vim-matchup = mkNvimPlugin inputs.vim-matchup "vim-matchup"; 42 | telescope = mkNvimPlugin inputs.telescope "telescope.nvim"; 43 | telescope_hoogle = mkNvimPlugin inputs.telescope_hoogle "telescope_hoogle"; 44 | telescope-smart-history = mkNvimPlugin inputs.telescope-smart-history "telescope-smart-history.nvim"; 45 | todo-comments = mkNvimPlugin inputs.todo-comments "todo-comments.nvim"; 46 | lualine = mkNvimPlugin inputs.lualine "lualine"; 47 | oil-nvim = mkNvimPlugin inputs.oil-nvim "oil.nvim"; 48 | harpoon = mkNvimPlugin inputs.harpoon "harpoon"; 49 | gitsigns = mkNvimPlugin inputs.gitsigns "gitsigns.nvim"; 50 | nvim-bqf = mkNvimPlugin inputs.nvim-bqf "nvim-bqf"; 51 | quicker-nvim = mkNvimPlugin inputs.quicker-nvim "quicker.nvim"; 52 | yanky = mkNvimPlugin inputs.yanky "yanky.nvim"; 53 | statuscol = mkNvimPlugin inputs.statuscol "statuscol"; 54 | nvim-unception = mkNvimPlugin inputs.nvim-unception "nvim-unception"; 55 | term-edit-nvim = mkNvimPlugin inputs.term-edit-nvim "term-edit.nvim"; 56 | other-nvim = mkNvimPlugin inputs.other-nvim "other.nvim"; 57 | which-key-nvim = mkNvimPlugin inputs.which-key-nvim "which-key.nvim"; 58 | snacks-nvim = mkNvimPlugin inputs.snacks-nvim "snacks.nvim"; 59 | fff-nvim = inputs.fff-nvim.packages.${system}.fff-nvim; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/java.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | require('mrcjk.neotest') 5 | 6 | local bufnr = vim.api.nvim_get_current_buf() 7 | 8 | if vim.fn.executable('pre-commit') == 1 then 9 | vim.keymap.set('n', 'pf', function() 10 | vim.system({ 'pre-commit', 'run', '--file', vim.api.nvim_buf_get_name(bufnr), 'java-format' }, nil, function() 11 | vim.schedule(function() 12 | vim.cmd.checktime() 13 | end) 14 | end) 15 | end, { noremap = true, silent = true, buffer = bufnr, desc = 'pre-commit run java-format' }) 16 | end 17 | 18 | local jdtls_bin = vim.fn.executable('jdtls') == 1 and 'jdtls' 19 | or vim.fn.executable('jdt-language-server') == 1 and 'jdt-language-server' 20 | 21 | if not jdtls_bin then 22 | return 23 | end 24 | 25 | local lsp = require('mrcjk.lsp') 26 | local jdtls = require('jdtls') 27 | 28 | vim.keymap.set('n', 'ot', function() 29 | vim.cmd.Other('test') 30 | end, { noremap = true, silent = true, desc = '[o]ther: [t]est', buffer = bufnr }) 31 | 32 | local on_jdtls_attach = function(_, buf) 33 | -- With `hotcodereplace = 'auto' the debug adapter will try to apply code changes 34 | -- you make during a debug session immediately. 35 | -- You can use the `JdtHotcodeReplace` command to trigger it manually 36 | -- jdtls.setup_dap { 37 | -- hotcodereplace = 'auto', 38 | -- } 39 | local function desc(description) 40 | return { noremap = true, silent = true, desc = description, buffer = buf } 41 | end 42 | vim.keymap.set('n', 'oi', jdtls.organize_imports, desc('java: [o]rganize [i]mports')) 43 | vim.keymap.set('n', 'ev', jdtls.extract_variable, desc('java: [e]xtract [v]ariable')) 44 | vim.keymap.set('v', 'ev', function() 45 | jdtls.extract_variable { visual = true } 46 | end, desc('java: [e]xtract [v]ariable (visual)')) 47 | vim.keymap.set('v', 'em', function() 48 | jdtls.extract_method { visual = true } 49 | end, desc('java: [e]xtract [m]ethod (visual)')) 50 | -- nvim-dap (requires java-debug and vscode-java-test bundles) 51 | -- vim.keymap.set('n', 'df', jdtls.test_class, opts) 52 | -- vim.keymap.set('n', 'dn', jdtls.test_nearest_method, opts) 53 | end 54 | 55 | local workspace_dir = vim.fs.dirname(vim.fs.find({ 'gradlew', 'mvnw', '.git' }, { upward = true })[1]) 56 | 57 | local cmd = { jdtls_bin } 58 | 59 | local java_runtime_dir = os.getenv('JAVA_RUNTIME_DIR') 60 | 61 | local settings = { 62 | java = { 63 | signatureHelp = { enabled = true }, 64 | contentProvider = { preferred = 'fernflower' }, 65 | completion = { 66 | favoriteStaticMembers = { 67 | 'org.junit.jupiter.api.Assertions.*', 68 | 'org.mockito.Mockito.*', 69 | 'io.vavr.API.$', 70 | 'io.vavr.API.Case', 71 | 'io.vavr.API.Match', 72 | 'io.vavr.API.For', 73 | 'io.vavr.Predicates.not', 74 | }, 75 | }, 76 | sources = { 77 | organizeImports = { 78 | starThreshold = 9999, 79 | staticStarThreshold = 9999, 80 | }, 81 | }, 82 | codeGeneration = { 83 | toString = { 84 | template = '${object.className}{${member.name()}=${member.value}, ${otherMembers}}', 85 | }, 86 | }, 87 | configuration = { 88 | runtimes = java_runtime_dir and { 89 | { 90 | name = 'Java', 91 | path = java_runtime_dir, 92 | } or nil, 93 | }, 94 | }, 95 | }, 96 | } 97 | 98 | jdtls.start_or_attach { 99 | capabilities = lsp.capabilities, 100 | cmd = cmd, 101 | root_dir = workspace_dir, 102 | settings = settings, 103 | on_attach = on_jdtls_attach, 104 | ---@param client vim.lsp.Client 105 | on_init = function(client, _) 106 | client:notify(vim.lsp.protocol.Methods.workspace_didChangeConfiguration, { settings = settings }) 107 | end, 108 | } 109 | -------------------------------------------------------------------------------- /nvim/plugin/other.lua: -------------------------------------------------------------------------------- 1 | require('other-nvim').setup { 2 | transformers = { 3 | lowercase = function(inputString) 4 | return inputString:lower() 5 | end, 6 | }, 7 | mappings = { 8 | -- Haskell 9 | { 10 | pattern = '(.+)/src/(.*)/(.*).hs$', 11 | target = { 12 | { 13 | target = '%1/test/%2/%3Spec.hs', 14 | context = 'spec', 15 | }, 16 | { 17 | target = '%1/spec/%2/%3Spec.hs', 18 | context = 'spec', 19 | }, 20 | { 21 | target = '%1/test/%2/*%3Test.hs', 22 | context = 'test', 23 | }, 24 | { 25 | target = '%1/src/%2/*%3.Internal.hs', 26 | context = 'internal', 27 | }, 28 | -- FIXME: 29 | -- { 30 | -- target = '/nix/configs/**/.*%3*.nix', 31 | -- context = 'nix', 32 | -- transformer = 'lowercase', 33 | -- }, 34 | }, 35 | }, 36 | { 37 | pattern = '(.+)/src/(.*)/(.*)/Internal.hs$', 38 | target = { 39 | { 40 | target = '%1/src/%2/%3.hs', 41 | context = 'public', 42 | }, 43 | { 44 | target = '%1/test/%2/%3Spec.hs', 45 | context = 'spec', 46 | }, 47 | { 48 | target = '%1/spec/%2/%3Spec.hs', 49 | context = 'spec', 50 | }, 51 | { 52 | target = '%1/test/%2/*%3Test.hs', 53 | context = 'test', 54 | }, 55 | }, 56 | }, 57 | { 58 | pattern = '(.+)/test/(.*)/(.*)Spec.hs$', 59 | target = '%1/src/%2/%3.hs', 60 | contect = 'impl', 61 | }, 62 | { 63 | pattern = '(.+)/test/(.*)/(.*)Spec.hs$', 64 | target = '%1/src/%2/%3/Internal.hs', 65 | contect = 'internal-impl', 66 | }, 67 | { 68 | pattern = '(.+)/test/(.*)/(.*)Test.hs$', 69 | target = '%1/src/%2/%3.hs', 70 | contect = 'impl', 71 | }, 72 | { 73 | pattern = '(.+)/test/(.*)/(.*)Test.hs$', 74 | target = '%1/src/%2/%3/Internal.hs', 75 | contect = 'internal-impl', 76 | }, 77 | { 78 | pattern = '(.+)/test/(.*)/Tiko(.*)Test.hs$', 79 | target = '%1/src/%2/%3.hs', 80 | contect = 'impl', 81 | }, 82 | { 83 | pattern = '(.+)/test/(.*)/Tiko(.*)Test.hs$', 84 | target = '%1/src/%2/%3/Internal.hs', 85 | contect = 'internal-impl', 86 | }, 87 | { 88 | pattern = '(.+)/spec/(.*)/(.*)Spec.hs$', 89 | target = '%1/src/%2/%3.hs', 90 | contect = 'impl', 91 | }, 92 | { 93 | pattern = '(.+)/spec/(.*)/(.*)Spec.hs$', 94 | target = '%1/src/%2/%3/Internal.hs', 95 | contect = 'internal-impl', 96 | }, 97 | { 98 | pattern = '(.+)/spec/(.*)/(.*)Test.hs$', 99 | target = '%1/src/%2/%3.hs', 100 | contect = 'impl', 101 | }, 102 | { 103 | pattern = '(.+)/spec/(.*)/(.*)Test.hs$', 104 | target = '%1/src/%2/%3/Internal.hs', 105 | contect = 'internal-impl', 106 | }, 107 | { 108 | pattern = '(.+)/spec/(.*)/Tiko(.*)Test.hs$', 109 | target = '%1/src/%2/%3.hs', 110 | contect = 'impl', 111 | }, 112 | { 113 | pattern = '(.+)/spec/(.*)/Tiko(.*)Test.hs$', 114 | target = '%1/src/%2/%3/Internal.hs', 115 | contect = 'internal-impl', 116 | }, 117 | 118 | -- Java 119 | { 120 | pattern = '(.+)/src/main/(.*)/(.*).java$', 121 | target = { 122 | { 123 | target = '%1/src/test/%2/%3Test.java', 124 | context = 'test', 125 | }, 126 | }, 127 | }, 128 | { 129 | pattern = '(.+)/src/test/(.*)/(.*)Test.java$', 130 | target = { 131 | { 132 | target = '%1/src/main/%2/%3.java', 133 | }, 134 | }, 135 | }, 136 | }, 137 | } 138 | 139 | vim.keymap.set('n', 'oo', function() 140 | vim.cmd.Other() 141 | end, { noremap = true, silent = true, desc = '[o]ther' }) 142 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/haskell.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | local bufnr = vim.api.nvim_get_current_buf() 5 | require('lang.haskell').set_keymaps() 6 | require('mrcjk.neotest') 7 | local ht = require('haskell-tools') 8 | 9 | local function desc(description) 10 | return { noremap = true, silent = true, buffer = bufnr, desc = description } 11 | end 12 | vim.keymap.set('n', 'rr', ht.repl.toggle, desc('haskell: toggle [rr]epl')) 13 | vim.keymap.set('n', 'rl', ht.repl.reload, desc('haskell: [r]epl re[l]oad')) 14 | vim.keymap.set('n', 'rf', function() 15 | ht.repl.toggle(vim.api.nvim_buf_get_name(bufnr)) 16 | end, desc('haskell: [r]epl toggle with [f]ile')) 17 | vim.keymap.set('n', 'rq', ht.repl.quit, desc('haskell: [r]epl [q]uit')) 18 | vim.keymap.set('n', 'rp', ht.repl.paste, desc('haskell: [r]epl [p]aste')) 19 | vim.keymap.set('n', 'rt', ht.repl.paste_type, desc('haskell: [r]epl paste [t]ype from ')) 20 | vim.keymap.set('n', 'rw', ht.repl.cword_type, desc('haskell: [r]epl type of ')) 21 | -- TODO: remove when ambiguous target issue is resolved 22 | vim.keymap.set( 23 | 'n', 24 | 'tt', 25 | 'TermExec cmd="cabal repl %"', 26 | desc('haskell: start `cabal repl %` in [tt]erminal') 27 | ) 28 | 29 | vim.keymap.set( 30 | 'n', 31 | 'a', 32 | 'HaskellHoverAction', 33 | { noremap = true, silent = true, desc = 'haskell: hover [a]ction' } 34 | ) 35 | 36 | vim.keymap.set('n', 'os', function() 37 | vim.cmd.Other('spec') 38 | end, desc('[o]ther: [s]pec')) 39 | 40 | vim.keymap.set('n', 'ot', function() 41 | vim.cmd.Other('test') 42 | end, desc('[o]ther: [t]est')) 43 | 44 | vim.keymap.set('n', 'oi', function() 45 | vim.cmd.Other('impl') 46 | end, desc('[o]ther: [i]mpl')) 47 | 48 | vim.keymap.set('n', 'oI', function() 49 | vim.cmd.Other('internal-impl') 50 | end, desc('[o]ther: [I]nternal-impl')) 51 | 52 | vim.keymap.set('n', 'on', function() 53 | vim.cmd.Other('internal') 54 | end, desc('[o]ther: i[n]ternal')) 55 | 56 | if vim.fn.executable('pre-commit') == 1 then 57 | vim.keymap.set('n', 'pf', function() 58 | vim.system({ 'pre-commit', 'run', '--file', vim.api.nvim_buf_get_name(bufnr), 'fourmolu' }, nil, function() 59 | vim.schedule(function() 60 | vim.cmd.checktime() 61 | end) 62 | end) 63 | end, { noremap = true, silent = true, buffer = bufnr, desc = 'pre-commit run fourmolu' }) 64 | end 65 | 66 | -- nvim-surround 67 | 68 | if not vim.g.nvim_surround_setup then 69 | require('nvim-surround').setup() 70 | vim.g.nvim_surround_setup = true 71 | end 72 | 73 | ---@param items string[] 74 | ---@param prompt string? 75 | ---@param label_fn (fun(item:string):string) | nil 76 | ---@return string|nil 77 | local function ui_select_sync(items, prompt, label_fn) 78 | label_fn = label_fn or function(str) 79 | return str 80 | end 81 | local choices = { prompt } 82 | for i, item in ipairs(items) do 83 | table.insert(choices, string.format('%d: %s', i, label_fn(item))) 84 | end 85 | local choice = vim.fn.inputlist(choices) 86 | if choice < 1 or choice > #items then 87 | return nil 88 | end 89 | return items[choice] 90 | end 91 | 92 | ---@diagnostic disable-next-line: missing-fields 93 | require('nvim-surround').buffer_setup { 94 | surrounds = { 95 | ---@diagnostic disable-next-line: missing-fields 96 | ['L'] = { 97 | add = function() 98 | local level = ui_select_sync({ 'e', 'w', 'i', 'd', 't' }, 'Select a log level') 99 | return level and { { 100 | '[Log.' .. level .. '|', 101 | }, { '|]' } } or {} 102 | end, 103 | -- TODO: Use tree-sitter query (see ["f"]) in nvim-surround 104 | }, 105 | ---@diagnostic disable-next-line: missing-fields 106 | ['Q'] = { 107 | add = { '[qq|', '|]' }, 108 | }, 109 | }, 110 | } 111 | -------------------------------------------------------------------------------- /lib/lua/lang/clangd.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@brief 4 | --- 5 | --- https://clangd.llvm.org/installation.html 6 | --- 7 | --- ``` 8 | --- ln -s /path/to/myproject/build/compile_commands.json /path/to/myproject/ 9 | --- ``` 10 | --- - clangd relies on a [JSON compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) 11 | --- specified as compile_commands.json, see https://clangd.llvm.org/installation#compile_commandsjson 12 | 13 | -- https://clangd.llvm.org/extensions.html#switch-between-sourceheader 14 | local function switch_source_header(bufnr, client) 15 | local method_name = 'textDocument/switchSourceHeader' 16 | ---@diagnostic disable-next-line:param-type-mismatch 17 | if not client or not client:supports_method(method_name) then 18 | return vim.notify(('method %s is not supported by any servers active on the current buffer'):format(method_name)) 19 | end 20 | local params = vim.lsp.util.make_text_document_params(bufnr) 21 | ---@diagnostic disable-next-line:param-type-mismatch 22 | client:request(method_name, params, function(err, result) 23 | if err then 24 | error(tostring(err)) 25 | end 26 | if not result then 27 | vim.notify('corresponding file cannot be determined') 28 | return 29 | end 30 | vim.cmd.edit(vim.uri_to_fname(result)) 31 | end, bufnr) 32 | end 33 | 34 | local function symbol_info(bufnr, client) 35 | local method_name = 'textDocument/symbolInfo' 36 | ---@diagnostic disable-next-line:param-type-mismatch 37 | if not client or not client:supports_method(method_name) then 38 | return vim.notify('Clangd client not found', vim.log.levels.ERROR) 39 | end 40 | local win = vim.api.nvim_get_current_win() 41 | local params = vim.lsp.util.make_position_params(win, client.offset_encoding) 42 | ---@diagnostic disable-next-line:param-type-mismatch 43 | client:request(method_name, params, function(err, res) 44 | if err or #res == 0 then 45 | -- Clangd always returns an error, there is no reason to parse it 46 | return 47 | end 48 | local container = string.format('container: %s', res[1].containerName) ---@type string 49 | local name = string.format('name: %s', res[1].name) ---@type string 50 | vim.lsp.util.open_floating_preview({ name, container }, '', { 51 | height = 2, 52 | width = math.max(string.len(name), string.len(container)), 53 | focusable = false, 54 | focus = false, 55 | title = 'Symbol Info', 56 | }) 57 | end, bufnr) 58 | end 59 | 60 | ---@class ClangdInitializeResult: lsp.InitializeResult 61 | ---@field offsetEncoding? string 62 | 63 | local root_files = { 64 | '.clangd', 65 | '.clang-tidy', 66 | '.clang-format', 67 | 'compile_commands.json', 68 | 'compile_flags.txt', 69 | 'configure.ac', -- AutoTools 70 | } 71 | 72 | function M.launch() 73 | vim.lsp.start { 74 | cmd = { 'clangd' }, 75 | root_dir = vim.fs.dirname(vim.fs.find(root_files, { upward = true })[1]), 76 | filetypes = { 'c', 'cpp' }, 77 | single_file_support = true, 78 | capabilities = vim.tbl_deep_extend('force', { 79 | textDocument = { 80 | completion = { 81 | editsNearCursor = true, 82 | }, 83 | }, 84 | offsetEncoding = { 'utf-8', 'utf-16' }, 85 | }, require('mrcjk.lsp').capabilities), 86 | ---@param init_result ClangdInitializeResult 87 | on_init = function(client, init_result) 88 | if init_result.offsetEncoding then 89 | client.offset_encoding = init_result.offsetEncoding 90 | end 91 | end, 92 | on_attach = function(client, bufnr) 93 | local function clangd(opts) 94 | local fargs = opts.fargs 95 | local cmd = table.remove(fargs, 1) 96 | 97 | if cmd == 'switchSourceHeader' then 98 | return switch_source_header(bufnr, client) 99 | end 100 | if cmd == 'showSymbolInfo' then 101 | return symbol_info(bufnr, client) 102 | end 103 | vim.notify('unknown Clangd command: ' .. cmd, vim.log.levels.ERROR) 104 | end 105 | 106 | vim.api.nvim_create_user_command('Clangd', clangd, { 107 | nargs = '+', 108 | desc = 'Clangd LSP client commands', 109 | complete = function(_) 110 | return { 'switchSourceHeader', 'showSymbolInfo' } 111 | end, 112 | }) 113 | end, 114 | } 115 | end 116 | 117 | return M 118 | -------------------------------------------------------------------------------- /nvim/after/ftplugin/rust.lua: -------------------------------------------------------------------------------- 1 | local files = require('mrcjk.files') 2 | files.treesitter_start() 3 | 4 | require('mrcjk.neotest') 5 | local codelens = require('mrcjk.lsp.codelens') 6 | 7 | local bufnr = vim.api.nvim_get_current_buf() 8 | local function desc(description) 9 | return { noremap = true, silent = true, buffer = bufnr, desc = description } 10 | end 11 | 12 | vim.keymap.set('n', 'rdd', function() 13 | vim.cmd.RustLsp('debuggables') 14 | end, desc('[r]ust: [dd]ebuggables')) 15 | vim.keymap.set('n', 'rdl', function() 16 | vim.cmd.RustLsp { 'debuggables', bang = true } 17 | end, desc('[r]ust: run [d]ebuggables [l]ast')) 18 | vim.keymap.set('n', 'rr', function() 19 | vim.cmd.RustLsp('runnables') 20 | end, desc('[r]ust: [r]unnables')) 21 | vim.keymap.set('n', 'rl', function() 22 | vim.cmd.RustLsp { 'runnables', bang = true } 23 | end, desc('[r]ust: [r]unnables [l]ast')) 24 | vim.keymap.set('n', 'rtt', function() 25 | vim.cmd.RustLsp('testables') 26 | end, desc('[r]ust: [t]es[t]ables')) 27 | vim.keymap.set('n', 'rtl', function() 28 | vim.cmd.RustLsp { 'testables', bang = true } 29 | end, desc('[r]ust: run [t]estables [l]ast')) 30 | vim.keymap.set('n', 'rme', function() 31 | vim.cmd.RustLsp('expandMacro') 32 | end, desc('[r]ust: [m]acro [e]xpand')) 33 | vim.keymap.set('n', 'rk', function() 34 | vim.cmd.RustLsp { 'moveItem', 'up' } 35 | end, desc('[r]ust: move item up [k]')) 36 | vim.keymap.set('n', 'rj', function() 37 | vim.cmd.RustLsp { 'moveItem', 'down' } 38 | end, desc('[r]ust: move item down [j]')) 39 | vim.keymap.set('v', 'K', function() 40 | vim.cmd.RustLsp { 'hover', 'range' } 41 | end, desc('rust: hover range')) 42 | vim.keymap.set('n', 'K', function() 43 | vim.cmd.RustLsp { 'hover', 'actions' } 44 | end, desc('rust: hover range')) 45 | vim.keymap.set('n', 're', function() 46 | vim.cmd.RustLsp('explainError') 47 | end, desc('[r]ust: [e]xplain error')) 48 | vim.keymap.set('n', 'rd', function() 49 | vim.cmd.RustLsp('renderDiagnostic') 50 | end, desc('rust: [r]ender [d]iagnostic')) 51 | vim.keymap.set('n', 'gd', function() 52 | vim.cmd.RustLsp('relatedDiagnostics') 53 | end, desc('rust: [g]o to related [d]iagnostics')) 54 | vim.keymap.set('n', 'gc', function() 55 | vim.cmd.RustLsp('openCargo') 56 | end, desc('rust: [g]o to [c]argo.toml')) 57 | vim.keymap.set('n', 'gp', function() 58 | vim.cmd.RustLsp('parentModule') 59 | end, desc('rust: [g]o to [p]arent module')) 60 | vim.keymap.set('n', 'J', function() 61 | vim.cmd.RustLsp('joinLines') 62 | end, desc('rust: join lines')) 63 | vim.keymap.set('n', 'rs', function() 64 | vim.cmd.RustLsp('ssr') 65 | end, desc('[r]ust: [s]sr')) 66 | 67 | ---@param lens lsp.CodeLens 68 | ---@return boolean 69 | local testable_predicate = function(lens) 70 | local title = vim.tbl_get(lens, 'command', 'title') 71 | return title and title:find('Run Test') ~= nil or false 72 | end 73 | 74 | vim.keymap.set('n', '[t', function() 75 | codelens.goto_prev { predicate = testable_predicate } 76 | end, desc('rust: previous [t]estable')) 77 | 78 | vim.keymap.set('n', ']t', function() 79 | codelens.goto_next { predicate = testable_predicate } 80 | end, desc('rust: next [t]estable')) 81 | 82 | vim.keymap.set( 83 | 'n', 84 | 'a', 85 | 'RustHoverAction', 86 | { noremap = true, silent = true, desc = 'rust: hover [a]ction' } 87 | ) 88 | 89 | -- vim.api.nvim_create_autocmd('User', { 90 | -- pattern = 'rustaceanvim.code_action', 91 | -- callback = function(ctx) 92 | -- vim.keymap.set( 93 | -- 'n', 94 | -- '', 95 | -- 'rustaceanvim.code_action.confirm', 96 | -- { noremap = true, silent = true, desc = 'confirm rust code action', buffer = ctx.data.buf } 97 | -- ) 98 | -- 99 | -- vim.keymap.set( 100 | -- 'n', 101 | -- '', 102 | -- 'rustaceanvim.code_action.confirm', 103 | -- { noremap = true, silent = true, desc = 'confirm rust code action', buffer = ctx.data.buf } 104 | -- ) 105 | -- 106 | -- vim.keymap.set( 107 | -- 'n', 108 | -- 'l', 109 | -- 'rustaceanvim.code_action.confirm', 110 | -- { noremap = true, silent = true, desc = 'confirm rust code action', buffer = ctx.data.buf } 111 | -- ) 112 | -- vim.keymap.set( 113 | -- 'n', 114 | -- 'q', 115 | -- 'rustaceanvim.code_action.quit', 116 | -- { noremap = true, silent = true, desc = 'confirm rust code action', buffer = ctx.data.buf } 117 | -- ) 118 | -- vim.keymap.set( 119 | -- 'n', 120 | -- 'h', 121 | -- 'rustaceanvim.code_action.quit', 122 | -- { noremap = true, silent = true, desc = 'confirm rust code action', buffer = ctx.data.buf } 123 | -- ) 124 | -- end, 125 | -- }) 126 | -------------------------------------------------------------------------------- /nvim/plugin/theme.lua: -------------------------------------------------------------------------------- 1 | if vim.g.theme_did_setup then 2 | return 3 | end 4 | vim.g.theme_did_setup = true 5 | 6 | local catppuccin = require('catppuccin') 7 | 8 | local compile_path = vim.fn.stdpath('cache') .. '/catppuccin' 9 | local flavour = vim.uv.os_getenv('CATPPUCCIN_FLAVOUR') or 'mocha' 10 | 11 | -- HACK: There seems to be a bug that causes recompilation on each start. 12 | -- This disables compilation. 13 | if not vim.uv.fs_stat(compile_path) then 14 | catppuccin.flavours = { latte = 1, frappe = 2, macchiato = 3, mocha = 4 } 15 | else 16 | catppuccin.flavours = {} 17 | end 18 | 19 | catppuccin.setup { 20 | -- no_italic = true, 21 | flavour = flavour, 22 | compile_path = compile_path, 23 | term_colors = true, 24 | transparent_background = false, 25 | ---@type CtpFlavors> 26 | color_overrides = { 27 | ---@class CtpColors 28 | mocha = { 29 | base = '#202020', 30 | mantle = '#202020', 31 | crust = '#202020', 32 | }, 33 | }, 34 | ---@param colors CtpColors 35 | custom_highlights = function(colors) 36 | local darkening_percentage = 0.095 37 | local U = require('catppuccin.utils.colors') 38 | return { 39 | TelescopeResultsTitle = { bg = colors.green, fg = colors.base }, 40 | TelescopePromptTitle = { bg = colors.yellow, fg = colors.base }, 41 | TelescopePreviewTitle = { bg = colors.red, fg = colors.base }, 42 | TermCursor = { link = 'Cursor' }, 43 | TermCursorNC = { bg = colors.red, fg = colors.text, ctermbg = 1, ctermfg = 15 }, 44 | LspCodeLens = { fg = colors.mauve, bg = U.darken(colors.mauve, darkening_percentage, colors.base), italic = true }, 45 | FidgetTitle = { link = 'DiagnosticHint' }, 46 | FloatBorder = { fg = colors.base, bg = colors.base }, 47 | DiagnosticFloatingError = { bg = U.darken(colors.red, darkening_percentage, colors.base) }, 48 | DiagnosticFloatingWarn = { bg = U.darken(colors.yellow, darkening_percentage, colors.base) }, 49 | DiagnosticFloatingHint = { bg = U.darken(colors.green, darkening_percentage, colors.base) }, 50 | DiagnosticFloatingOk = { bg = U.darken(colors.green, darkening_percentage, colors.base) }, 51 | } 52 | end, 53 | integrations = { 54 | fidget = true, 55 | flash = true, 56 | fzf = false, 57 | harpoon = true, 58 | indent_blankline = { enabled = true }, 59 | cmp = false, 60 | gitsigns = true, 61 | nvimtree = false, 62 | notify = false, 63 | mini = { enabled = false }, 64 | mason = false, 65 | markdown = true, 66 | neogit = false, 67 | neotest = true, 68 | dap = false, 69 | dap_ui = false, 70 | semantic_tokens = true, 71 | nvim_surround = true, 72 | treesitter = true, 73 | treesitter_context = true, 74 | ts_rainbow2 = true, 75 | ufo = false, 76 | which_key = true, 77 | vimwiki = true, 78 | -- For more plugins integrations please scroll down (https://github.com/catppuccin/nvim#integrations) 79 | }, 80 | } 81 | vim.cmd.colorscheme('catppuccin') 82 | 83 | vim.api.nvim_create_autocmd({ 'FileType', 'TermOpen' }, { 84 | once = true, 85 | pattern = '*', 86 | group = vim.api.nvim_create_augroup('catppuccin-nvim-setup', {}), 87 | callback = function() 88 | if vim.g.lualine_nvim_did_setup then 89 | return 90 | end 91 | vim.g.lualine_nvim_did_setup = true 92 | -- XXX: lualine needs to be setup after setting the colorscheme 93 | 94 | ---@return string status 95 | local function extra_mode_status() 96 | local reg_recording = vim.fn.reg_recording() 97 | if reg_recording ~= '' then 98 | return ' @' .. reg_recording 99 | end 100 | local reg_executing = vim.fn.reg_executing() 101 | if reg_executing ~= '' then 102 | return ' @' .. reg_executing 103 | end 104 | local mode = vim.api.nvim_get_mode().mode 105 | if mode == 'ix' then 106 | return '^X: (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)' 107 | end 108 | return '' 109 | end 110 | 111 | local catppuccin_lualine = require('catppuccin.utils.lualine')(flavour) 112 | local C = require('catppuccin.palettes').get_palette(flavour) 113 | catppuccin_lualine.normal.a.bg = C.mauve 114 | catppuccin_lualine.visual.a.bg = C.blue 115 | 116 | require('lualine').setup { 117 | globalstatus = true, 118 | sections = { 119 | lualine_c = {}, 120 | lualine_z = { 121 | { extra_mode_status }, 122 | }, 123 | }, 124 | options = { 125 | theme = catppuccin_lualine, 126 | }, 127 | tabline = { 128 | lualine_a = { 129 | { 130 | 'tabs', 131 | mode = 1, 132 | }, 133 | }, 134 | lualine_b = { 135 | { 136 | 'buffers', 137 | show_filename_only = true, 138 | show_bufnr = true, 139 | mode = 4, 140 | filetype_names = { 141 | TelescopePrompt = 'Telescope', 142 | dashboard = 'Dashboard', 143 | packer = 'Packer', 144 | fzf = 'FZF', 145 | alpha = 'Alpha', 146 | }, 147 | buffers_color = { 148 | -- Same values as the general color option can be used here. 149 | active = 'lualine_b_normal', -- Color for active buffer. 150 | inactive = 'lualine_b_inactive', -- Color for inactive buffer. 151 | }, 152 | }, 153 | }, 154 | lualine_c = {}, 155 | lualine_x = {}, 156 | lualine_y = {}, 157 | lualine_z = {}, 158 | }, 159 | winbar = { 160 | lualine_z = { 161 | { 162 | 'filename', 163 | path = 1, 164 | file_status = true, 165 | newfile_status = true, 166 | }, 167 | }, 168 | }, 169 | extensions = { 'quickfix' }, 170 | } 171 | end, 172 | }) 173 | -------------------------------------------------------------------------------- /lib/lua/mrcjk/files.lua: -------------------------------------------------------------------------------- 1 | local files = {} 2 | 3 | local disabled_files = { 4 | 'Enums.hs', 5 | 'all-packages.nix', 6 | 'hackage-packages.nix', 7 | 'generated.nix', 8 | } 9 | 10 | ---@param bufnr number 11 | ---@return boolean 12 | local function disable_treesitter_features(bufnr) 13 | local fname = vim.api.nvim_buf_get_name(bufnr) 14 | local short_name = vim.fn.fnamemodify(fname, ':t') 15 | if vim.tbl_contains(disabled_files, short_name) then 16 | return true 17 | end 18 | local max_filesize = 100 * 1024 -- 100 KiB 19 | local ok, stats = pcall(vim.uv.fs_stat, fname) 20 | if ok and stats and stats.size > max_filesize then 21 | return true 22 | end 23 | return false 24 | end 25 | 26 | vim.cmd.packadd('nvim-treesitter') 27 | ---@diagnostic disable: missing-fields 28 | local configs = require('nvim-treesitter.configs') 29 | configs.setup { 30 | matchup = { 31 | enable = true, -- mandatory, false will disable the whole extension 32 | disable = { 'python' }, 33 | }, 34 | incremental_selection = { 35 | enable = false, 36 | }, 37 | } 38 | 39 | require('treesitter-context').setup { 40 | max_lines = 3, 41 | } 42 | 43 | require('nvim-treesitter-textobjects').setup { 44 | select = { 45 | -- Automatically jump forward to textobjects, similar to targets.vim 46 | lookahead = true, 47 | selection_modes = { 48 | ['@parameter.outer'] = 'v', -- charwise 49 | ['@function.outer'] = 'V', -- linewise 50 | ['@class.outer'] = '', -- blockwise 51 | }, 52 | }, 53 | move = { 54 | -- whether to set jumps in the jumplist 55 | set_jumps = true, 56 | }, 57 | } 58 | 59 | vim.cmd.packadd('vim-illuminate') 60 | local illuminate = require('illuminate') 61 | illuminate.configure { 62 | delay = 200, 63 | should_enable = function(bufnr) 64 | return not disable_treesitter_features(bufnr) 65 | end, 66 | } 67 | 68 | require('todo-comments').setup { 69 | highlight = { 70 | pattern = { 71 | [[.*<(KEYWORDS)\s*:]], 72 | [[.*<(KEYWORDS)\s*]], 73 | [[.*<(KEYWORDS)\(.*\)\s*:]], 74 | }, 75 | }, 76 | } 77 | 78 | ---@param lang? string 79 | ---@param bufnr? number 80 | function files.treesitter_start(lang, bufnr) 81 | bufnr = bufnr or vim.api.nvim_get_current_buf() 82 | lang = lang or vim.bo[bufnr].ft 83 | if disable_treesitter_features(bufnr) then 84 | return 85 | end 86 | vim.treesitter.start(bufnr, lang) 87 | 88 | if vim.treesitter.query.get(lang, 'indents') then 89 | vim.bo[bufnr].indentexpr = "v:lua.require('mrcjk.indentexpr').indentexpr()" 90 | else 91 | vim.bo[bufnr].autoindent = true 92 | vim.bo[bufnr].smartindent = true 93 | end 94 | 95 | -- select 96 | vim.keymap.set({ 'x', 'o' }, 'af', function() 97 | require('nvim-treesitter-textobjects.select').select_textobject('@function.outer', 'textobjects') 98 | end, { buffer = bufnr }) 99 | vim.keymap.set({ 'x', 'o' }, 'if', function() 100 | require('nvim-treesitter-textobjects.select').select_textobject('@function.inner', 'textobjects') 101 | end, { buffer = bufnr }) 102 | vim.keymap.set({ 'x', 'o' }, 'ac', function() 103 | require('nvim-treesitter-textobjects.select').select_textobject('@class.outer', 'textobjects') 104 | end, { buffer = bufnr }) 105 | vim.keymap.set({ 'x', 'o' }, 'ic', function() 106 | require('nvim-treesitter-textobjects.select').select_textobject('@class.inner', 'textobjects') 107 | end, { buffer = bufnr }) 108 | vim.keymap.set({ 'x', 'o' }, 'as', function() 109 | require('nvim-treesitter-textobjects.select').select_textobject('@local.scope', 'locals') 110 | end, { buffer = bufnr }) 111 | 112 | -- swap 113 | vim.keymap.set('n', 'a', function() 114 | require('nvim-treesitter-textobjects.swap').swap_next('@parameter.inner') 115 | end, { buffer = bufnr }) 116 | vim.keymap.set('n', 'A', function() 117 | require('nvim-treesitter-textobjects.swap').swap_previous('@parameter.outer') 118 | end, { buffer = bufnr }) 119 | 120 | -- move 121 | vim.keymap.set({ 'n', 'x', 'o' }, ']m', function() 122 | require('nvim-treesitter-textobjects.move').goto_next_start('@function.outer', 'textobjects') 123 | end, { buffer = bufnr, desc = '[m] next function (start)' }) 124 | vim.keymap.set({ 'n', 'x', 'o' }, ']M', function() 125 | require('nvim-treesitter-textobjects.move').goto_next_end('@function.outer', 'textobjects') 126 | end, { buffer = bufnr, desc = '[M] next function (end)' }) 127 | vim.keymap.set({ 'n', 'x', 'o' }, ']p', function() 128 | require('nvim-treesitter-textobjects.move').goto_next_start('@parameter.outer', 'textobjects') 129 | end, { buffer = bufnr, desc = '[p] next parameter (start)' }) 130 | vim.keymap.set({ 'n', 'x', 'o' }, ']P', function() 131 | require('nvim-treesitter-textobjects.move').goto_next_end('@parameter.outer', 'textobjects') 132 | end, { buffer = bufnr, desc = '[P] next parameter (end)' }) 133 | vim.keymap.set({ 'n', 'x', 'o' }, '[m', function() 134 | require('nvim-treesitter-textobjects.move').goto_previous_start('@function.outer', 'textobjects') 135 | end, { buffer = bufnr, desc = '[m] previous function (start)' }) 136 | vim.keymap.set({ 'n', 'x', 'o' }, '[M', function() 137 | require('nvim-treesitter-textobjects.move').goto_previous_end('@function.outer', 'textobjects') 138 | end, { buffer = bufnr, desc = '[M] previous function (end)' }) 139 | vim.keymap.set({ 'n', 'x', 'o' }, '[p', function() 140 | require('nvim-treesitter-textobjects.move').goto_previous_start('@parameter.outer', 'textobjects') 141 | end, { buffer = bufnr, desc = 'previous [p]arameter (start)' }) 142 | vim.keymap.set({ 'n', 'x', 'o' }, '[P', function() 143 | require('nvim-treesitter-textobjects.move').goto_previous_end('@parameter.outer', 'textobjects') 144 | end, { buffer = bufnr, desc = 'previous [P]arameter (end)' }) 145 | 146 | -- illuminate 147 | vim.keymap.set('n', ']]', function() 148 | illuminate.goto_next_reference(true) 149 | end, { noremap = true, silent = true, buffer = bufnr, desc = 'next reference' }) 150 | vim.keymap.set('n', '[[', function() 151 | illuminate.goto_prev_reference(true) 152 | end, { noremap = true, silent = true, buffer = bufnr, desc = 'previous reference' }) 153 | 154 | vim.keymap.set('n', ']t', function() 155 | require('todo-comments').jump_next { keywords = { 'TODO' } } 156 | end, { buffer = bufnr, desc = 'Next [t]odo comment' }) 157 | 158 | vim.keymap.set('n', '[t', function() 159 | require('todo-comments').jump_prev { keywords = { 'TODO' } } 160 | end, { buffer = bufnr, desc = 'Previous [t]odo comment' }) 161 | end 162 | 163 | return files 164 | -------------------------------------------------------------------------------- /lib/lua/mrcjk/lsp/codelens.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | --- @class lsp.codelens.GetOpts 4 | --- @field lnum? integer 5 | --- @field predicate? fun(lens: lsp.CodeLens):boolean 6 | --- @field wrap? integer 7 | --- @field cursor_position? {[1]:integer,[2]:integer} 8 | --- @field win_id? integer 9 | 10 | --- @class lsp.codelens.GotoOpts: lsp.codelens.GetOpts 11 | 12 | --- @param predicate fun(lens: lsp.CodeLens):boolean Filter predicate 13 | --- @param lenses lsp.CodeLens[] 14 | --- @return table 15 | local function filter_lenses_by_lines(predicate, lenses) 16 | if not lenses then 17 | return {} 18 | end 19 | 20 | local lenses_by_lnum = {} --- @type table 21 | for _, lens in ipairs(lenses) do 22 | local line_lenses = lenses_by_lnum[lens.range.start.line] 23 | if not line_lenses then 24 | line_lenses = {} 25 | lenses_by_lnum[lens.range.start.line] = line_lenses 26 | end 27 | if predicate(lens) then 28 | table.insert(line_lenses, lens) 29 | end 30 | end 31 | return lenses_by_lnum 32 | end 33 | 34 | --- @param position {[1]: integer, [2]: integer} 35 | --- @param search_forward boolean 36 | --- @param bufnr integer 37 | --- @param opts lsp.codelens.GetOpts 38 | --- @return lsp.CodeLens? 39 | local function next_codelens(position, search_forward, bufnr, opts) 40 | position[1] = position[1] - 1 41 | bufnr = (not bufnr or bufnr == 0) and vim.api.nvim_get_current_buf() or bufnr 42 | local wrap = opts.wrap == nil and true or opts.wrap 43 | local predicate = opts.predicate or function() 44 | return true 45 | end 46 | local line_count = vim.api.nvim_buf_line_count(bufnr) 47 | local lenses = vim.lsp.codelens.get(bufnr) 48 | local lenses_by_lnum = filter_lenses_by_lines(predicate, lenses) 49 | 50 | for i = 0, line_count do 51 | local offset = i * (search_forward and 1 or -1) 52 | local lnum = position[1] + offset 53 | if lnum < 0 or lnum >= line_count then 54 | if not wrap then 55 | return 56 | end 57 | lnum = (lnum + line_count) % line_count 58 | end 59 | if lenses_by_lnum[lnum] and not vim.tbl_isempty(lenses_by_lnum[lnum]) then 60 | local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] 61 | --- @type function, function 62 | local sort_lenses, is_next 63 | if search_forward then 64 | ---@param a lsp.CodeLens 65 | ---@param b lsp.CodeLens 66 | sort_lenses = function(a, b) 67 | return a.range.start.character < b.range.start.character 68 | end 69 | ---@param d lsp.CodeLens 70 | is_next = function(d) 71 | return math.min(d.range.start.character, line_length - 1) > position[2] 72 | end 73 | else 74 | ---@param a lsp.CodeLens 75 | ---@param b lsp.CodeLens 76 | sort_lenses = function(a, b) 77 | return a.range.start.character > b.range.start.character 78 | end 79 | ---@param d lsp.CodeLens 80 | is_next = function(d) 81 | return math.min(d.range.start.character, line_length - 1) < position[2] 82 | end 83 | end 84 | table.sort(lenses_by_lnum[lnum], sort_lenses) 85 | if i == 0 then 86 | for _, v in 87 | pairs(lenses_by_lnum[lnum] --[[@as table]]) 88 | do 89 | if is_next(v) then 90 | return v 91 | end 92 | end 93 | else 94 | return lenses_by_lnum[lnum][1] 95 | end 96 | end 97 | end 98 | end 99 | 100 | --- @param opts lsp.codelens.GotoOpts? 101 | --- @param pos {[1]:integer,[2]:integer}|false 102 | local function codelens_move_pos(opts, pos) 103 | opts = opts or {} 104 | 105 | local win_id = opts.win_id or vim.api.nvim_get_current_win() 106 | 107 | if not pos then 108 | vim.api.nvim_echo({ { 'No more valid codelenses to move to', 'WarningMsg' } }, true, {}) 109 | return 110 | end 111 | 112 | vim.api.nvim_win_call(win_id, function() 113 | -- Save position in the window's jumplist 114 | vim.cmd("normal! m'") 115 | vim.api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] }) 116 | -- Open folds under the cursor 117 | vim.cmd('normal! zv') 118 | end) 119 | end 120 | 121 | --- Get the previous codelens closest to the cursor position. 122 | --- 123 | ---@param opts? lsp.codelens.GetOpts (table) See |lsp.codelens.goto_next()| 124 | ---@return lsp.CodeLens? Previous codelens 125 | function M.get_prev(opts) 126 | opts = opts or {} 127 | 128 | local win_id = opts.win_id or vim.api.nvim_get_current_win() 129 | local bufnr = vim.api.nvim_win_get_buf(win_id) 130 | local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id) 131 | 132 | return next_codelens(cursor_position, false, bufnr, opts) 133 | end 134 | 135 | --- Return the position of the previous codelens in the current buffer. 136 | --- 137 | ---@param opts? lsp.codelens.GetOpts (table) See |lsp.codelens.goto_next()| 138 | ---@return table|false: Previous codelens position as a (row, col) tuple or false if there is no 139 | --- prior codelens 140 | function M.get_prev_pos(opts) 141 | local prev = M.get_prev(opts) 142 | if not prev then 143 | return false 144 | end 145 | return { prev.range.start.line, prev.range.start.character } 146 | end 147 | 148 | --- Move to the previous codelens in the current buffer. 149 | ---@param opts? lsp.codelens.GotoOpts (table) See |lsp.codelens.goto_next()| 150 | function M.goto_prev(opts) 151 | return codelens_move_pos(opts, M.get_prev_pos(opts)) 152 | end 153 | 154 | --- Get the next codelens closest to the cursor position. 155 | --- 156 | ---@param opts? lsp.codelens.GetOpts (table) See |lsp.codelens.goto_next()| 157 | ---@return lsp.CodeLens? : Next codelens 158 | function M.get_next(opts) 159 | opts = opts or {} 160 | 161 | local win_id = opts.win_id or vim.api.nvim_get_current_win() 162 | local bufnr = vim.api.nvim_win_get_buf(win_id) 163 | local cursor_position = opts.cursor_position or vim.api.nvim_win_get_cursor(win_id) 164 | 165 | return next_codelens(cursor_position, true, bufnr, opts) 166 | end 167 | 168 | --- Return the position of the next codelens in the current buffer. 169 | --- 170 | ---@param opts? lsp.codelens.GotoOpts (table) See |lsp.codelens.goto_next()| 171 | ---@return table|false : Next codelens position as a (row, col) tuple or false if no next 172 | --- codelens. 173 | function M.get_next_pos(opts) 174 | local next = M.get_next(opts) 175 | if not next then 176 | return false 177 | end 178 | return { next.range.start.line, next.range.start.character } 179 | end 180 | 181 | --- Move to the next codelens. 182 | --- 183 | ---@param opts? lsp.codelens.GotoOpts (table) Configuration table with the following keys: 184 | --- - cursor_position: (cursor position) Cursor position as a (row, col) tuple. 185 | --- See |nvim_win_get_cursor()|. Defaults to the current cursor position. 186 | --- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'. 187 | --- - win_id: (number, default 0) Window ID 188 | --- - predicate: (function) A predicate for filtering code lenses. 189 | function M.goto_next(opts) 190 | codelens_move_pos(opts, M.get_next_pos(opts)) 191 | end 192 | 193 | return M 194 | -------------------------------------------------------------------------------- /nvim/plugin/telescope.lua: -------------------------------------------------------------------------------- 1 | local layout_config = { 2 | vertical = { 3 | width = function(_, max_columns) 4 | return math.floor(max_columns * 0.99) 5 | end, 6 | height = function(_, _, max_lines) 7 | return math.floor(max_lines * 0.99) 8 | end, 9 | prompt_position = 'bottom', 10 | preview_cutoff = 0, 11 | }, 12 | } 13 | 14 | local function flash(prompt_bufnr) 15 | require('flash').jump { 16 | pattern = '^', 17 | label = { after = { 0, 0 } }, 18 | search = { 19 | mode = 'search', 20 | exclude = { 21 | function(win) 22 | return vim.bo[vim.api.nvim_win_get_buf(win)].filetype ~= 'TelescopeResults' 23 | end, 24 | }, 25 | }, 26 | action = function(match) 27 | local picker = require('telescope.actions.state').get_current_picker(prompt_bufnr) 28 | picker:set_selection(match.pos[1] - 1) 29 | end, 30 | } 31 | end 32 | 33 | local lz = require('lz.n') 34 | local keymap = lz.keymap { 35 | 'telescope.nvim', 36 | cmd = 'Telescope', 37 | ft = 'haskell', 38 | before = function() 39 | lz.trigger_load('harpoon') 40 | end, 41 | after = function() 42 | local telescope = require('telescope') 43 | telescope.setup { 44 | defaults = { 45 | path_display = { 46 | 'truncate', 47 | }, 48 | layout_strategy = 'vertical', 49 | layout_config = layout_config, 50 | mappings = { 51 | i = { 52 | [''] = function(...) 53 | require('telescope.actions').send_to_qflist(...) 54 | end, 55 | [''] = function(...) 56 | require('telescope.actions').send_to_loclist(...) 57 | end, 58 | [''] = function(...) 59 | require('telescope.actions').cycle_previewers_next(...) 60 | end, 61 | [''] = function(...) 62 | require('telescope.actions').cycle_previewers_prev(...) 63 | end, 64 | [''] = flash, 65 | }, 66 | n = { 67 | q = function(...) 68 | require('telescope.actions').close(...) 69 | end, 70 | s = flash, 71 | }, 72 | }, 73 | preview = { 74 | treesitter = false, 75 | }, 76 | history = { 77 | path = vim.fn.stdpath('data') .. '/telescope_history.sqlite3', 78 | limit = 1000, 79 | }, 80 | color_devicons = true, 81 | set_env = { ['COLORTERM'] = 'truecolor' }, 82 | prompt_prefix = '  ', 83 | selection_caret = ' ', 84 | entry_prefix = ' ', 85 | initial_mode = 'insert', 86 | vimgrep_arguments = { 87 | 'rg', 88 | '-L', 89 | '--color=never', 90 | '--no-heading', 91 | '--with-filename', 92 | '--line-number', 93 | '--column', 94 | '--smart-case', 95 | }, 96 | borderchars = { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' }, 97 | }, 98 | extensions = { 99 | fzy_native = { 100 | override_generic_sorter = false, 101 | override_file_sorter = true, 102 | }, 103 | }, 104 | } 105 | 106 | telescope.load_extension('fzy_native') 107 | telescope.load_extension('smart_history') 108 | telescope.load_extension('harpoon') 109 | -- telescope.load_extension('cheat') 110 | -- telescope.load_extension('ui-select') 111 | end, 112 | } 113 | 114 | local function lazy_require(moduleName) 115 | return setmetatable({}, { 116 | __index = function(_, key) 117 | return function(...) 118 | local module = require(moduleName) 119 | return module[key](...) 120 | end 121 | end, 122 | }) 123 | end 124 | 125 | local builtin = lazy_require('telescope.builtin') 126 | 127 | local extensions = setmetatable({}, { 128 | __index = function(_, key) 129 | local telescope = require('telescope') 130 | if telescope.extensions[key] then 131 | return telescope.extensions[key] 132 | end 133 | telescope.load_extension(key) 134 | return telescope.extensions[key] 135 | end, 136 | }) 137 | 138 | local function grep_current_file_type(func, extra_args) 139 | local current_file_ext = vim.fn.expand('%:e') 140 | local additional_vimgrep_arguments = {} 141 | if current_file_ext ~= '' then 142 | additional_vimgrep_arguments = vim.list_extend(extra_args or {}, { 143 | '--type', 144 | current_file_ext, 145 | }) 146 | end 147 | local conf = require('telescope.config').values 148 | func { 149 | vimgrep_arguments = vim 150 | .iter({ 151 | conf.vimgrep_arguments, 152 | additional_vimgrep_arguments, 153 | }) 154 | :flatten() 155 | :totable(), 156 | } 157 | end 158 | 159 | local function grep_string_current_file_type() 160 | grep_current_file_type(builtin.grep_string) 161 | end 162 | 163 | local function live_grep_current_file_type() 164 | grep_current_file_type(builtin.live_grep) 165 | end 166 | 167 | local function fuzzy_grep(opts) 168 | opts = vim.tbl_extend('error', opts or {}, { search = '', prompt_title = 'Fuzzy grep' }) 169 | builtin.grep_string(opts) 170 | end 171 | 172 | local function fuzzy_grep_current_file_type() 173 | grep_current_file_type(fuzzy_grep) 174 | end 175 | 176 | keymap.set('n', 'tp', function() 177 | builtin.find_files() 178 | end, { desc = 'telescope: find files' }) 179 | keymap.set('n', 'tO', builtin.oldfiles, { desc = '[t]elescope: [O]ld files' }) 180 | keymap.set('n', 'ts', builtin.live_grep, { desc = '[t]elescope: live grep (regex [s]earch)' }) 181 | keymap.set('n', '', function() 182 | local conf = require('telescope.config').values 183 | builtin.live_grep { 184 | vimgrep_arguments = table.insert(conf.vimgrep_arguments, '-F'), 185 | } 186 | end, { desc = 'telescope: live grep (no regex)' }) 187 | keymap.set('n', 'tf', fuzzy_grep, { desc = '[t]elescope: [f]uzzy grep' }) 188 | keymap.set('n', '', fuzzy_grep_current_file_type, { desc = 'telescope: fuzzy grep filetype' }) 189 | keymap.set('n', '', live_grep_current_file_type, { desc = 'telescope: live grep filetype' }) 190 | keymap.set('n', 't*', grep_string_current_file_type, { desc = '[t]elescope: grep string [*] filetype' }) 191 | keymap.set('n', '*', builtin.grep_string, { desc = 'telescope: grep string' }) 192 | keymap.set('n', 'tc', builtin.quickfix, { desc = '[t]elescope: quickfix [c] list' }) 193 | keymap.set('n', 'tq', builtin.command_history, { desc = '[t]elescope: command [q] history' }) 194 | keymap.set('n', 'tl', builtin.loclist, { desc = '[t]elescope: [l]oclist' }) 195 | keymap.set('n', 'tr', builtin.registers, { desc = '[t]elescope: [r]egisters' }) 196 | keymap.set('n', 'tP', function() 197 | extensions.projects.projects() 198 | end, { desc = '[t]elescope: [P]rojects' }) 199 | keymap.set('n', 'ty', function() 200 | extensions.yank_history.yank_history() 201 | end, { desc = '[t]elescope: [y]ank history' }) 202 | keymap.set('n', 'tbb', builtin.buffers, { desc = '[t]elescope: [bb]uffers' }) 203 | keymap.set('n', 'tbf', builtin.current_buffer_fuzzy_find, { desc = '[t]elescope: [b]uffer [f]uzzy find' }) 204 | keymap.set('n', 'td', builtin.lsp_document_symbols, { desc = '[t]elescope: lsp [d]ocument symbols' }) 205 | keymap.set('n', 'tm', function() 206 | extensions.harpoon.marks() 207 | end, { desc = '[t]elescope: harpoon [m]arks' }) 208 | keymap.set('n', 'th', function() 209 | extensions.hoogle.hoogle { 210 | layout_strategy = 'vertical', 211 | layout_config = layout_config, 212 | } 213 | end, { desc = '[t]elescope: [h]oogle' }) 214 | keymap.set('n', 'tn', function() 215 | extensions.manix.manix() 216 | end, { desc = '[t]elescope: ma[n]ix' }) 217 | keymap.set('n', 'tN', function() 218 | extensions.manix.manix { cword = true } 219 | end, { desc = '[t]elescope: ma[N]ix ' }) 220 | keymap.set( 221 | 'n', 222 | 'to', 223 | builtin.lsp_dynamic_workspace_symbols, 224 | { desc = '[t]elescope: lsp dynamic w[o]rkspace symbols' } 225 | ) 226 | -------------------------------------------------------------------------------- /nvim/plugin/keymaps.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local fn = vim.fn 3 | local keymap = vim.keymap 4 | local diagnostic = vim.diagnostic 5 | 6 | -- Automatic management of search highlight 7 | local auto_hlsearch_namespace = vim.api.nvim_create_namespace('auto_hlsearch') 8 | vim.on_key(function(char) 9 | if vim.fn.mode() == 'n' then 10 | vim.opt.hlsearch = vim.tbl_contains({ '', 'n', 'N', '*', '#', '?', '/' }, vim.fn.keytrans(char)) 11 | end 12 | end, auto_hlsearch_namespace) 13 | 14 | -- Yank from current position till end of current line 15 | keymap.set('n', 'Y', 'y$', { silent = true, desc = 'yank to end of line' }) 16 | 17 | -- Buffer list navigation 18 | keymap.set('n', '[b', vim.cmd.bprevious, { silent = true, desc = 'previous [b]uffer' }) 19 | keymap.set('n', ']b', vim.cmd.bnext, { silent = true, desc = 'next [b]uffer' }) 20 | keymap.set('n', '[B', vim.cmd.bfirst, { silent = true, desc = 'first [B]uffer' }) 21 | keymap.set('n', ']B', vim.cmd.blast, { silent = true, desc = 'last [B]uffer' }) 22 | 23 | local function try_fallback_notify(opts) 24 | local success, _ = pcall(opts.try) 25 | if success then 26 | return 27 | end 28 | success, _ = pcall(opts.fallback) 29 | if success then 30 | return 31 | end 32 | vim.notify(opts.notify, vim.log.levels.INFO) 33 | end 34 | 35 | -- Cycle the quickfix and location lists 36 | local function cleft() 37 | try_fallback_notify { 38 | try = vim.cmd.cprev, 39 | fallback = vim.cmd.clast, 40 | notify = 'Quickfix list is empty!', 41 | } 42 | end 43 | 44 | local function cright() 45 | try_fallback_notify { 46 | try = vim.cmd.cnext, 47 | fallback = vim.cmd.cfirst, 48 | notify = 'Quickfix list is empty!', 49 | } 50 | end 51 | 52 | keymap.set('n', '[c', cleft, { silent = true, desc = '[c]ycle quickfix left' }) 53 | keymap.set('n', ']c', cright, { silent = true, desc = '[c]ycle quickfix right' }) 54 | keymap.set('n', '[C', vim.cmd.cfirst, { silent = true, desc = 'first quickfix entry [C]' }) 55 | keymap.set('n', ']C', vim.cmd.clast, { silent = true, desc = 'last quickfix entry [C]' }) 56 | 57 | local function lleft() 58 | try_fallback_notify { 59 | try = vim.cmd.lprev, 60 | fallback = vim.cmd.llast, 61 | notify = 'Location list is empty!', 62 | } 63 | end 64 | 65 | local function lright() 66 | try_fallback_notify { 67 | try = vim.cmd.lnext, 68 | fallback = vim.cmd.lfirst, 69 | notify = 'Location list is empty!', 70 | } 71 | end 72 | 73 | keymap.set('n', '[l', lleft, { silent = true, desc = 'cycle [l]oclist left' }) 74 | keymap.set('n', ']l', lright, { silent = true, desc = 'cycle [l]oclist right' }) 75 | keymap.set('n', '[L', vim.cmd.lfirst, { silent = true, desc = 'first [L]oclist entry' }) 76 | keymap.set('n', ']L', vim.cmd.llast, { silent = true, desc = 'last [L]oclist entry' }) 77 | 78 | -- Resize vertical splits 79 | local toIntegral = math.ceil 80 | keymap.set('n', 'w+', function() 81 | local curWinWidth = api.nvim_win_get_width(0) 82 | api.nvim_win_set_width(0, toIntegral(curWinWidth * 3 / 2)) 83 | end, { silent = true, desc = 'inc window [w]idth' }) 84 | keymap.set('n', 'w-', function() 85 | local curWinWidth = api.nvim_win_get_width(0) 86 | api.nvim_win_set_width(0, toIntegral(curWinWidth * 2 / 3)) 87 | end, { silent = true, desc = 'dec window [w]idth' }) 88 | keymap.set('n', 'h+', function() 89 | local curWinHeight = api.nvim_win_get_height(0) 90 | api.nvim_win_set_height(0, toIntegral(curWinHeight * 3 / 2)) 91 | end, { silent = true, desc = 'inc window [h]eight' }) 92 | keymap.set('n', 'h-', function() 93 | local curWinHeight = api.nvim_win_get_height(0) 94 | api.nvim_win_set_height(0, toIntegral(curWinHeight * 2 / 3)) 95 | end, { silent = true, desc = 'dec window [h]eight' }) 96 | 97 | -- Close floating windows 98 | keymap.set('n', 'fq', function() 99 | vim.cmd('fclose!') 100 | end, { silent = true, desc = '[f]loating windows [q]uit all' }) 101 | 102 | -- Remap Esc to switch to normal mode and Ctrl-Esc to pass Esc to terminal 103 | keymap.set('t', '', '', { desc = 'switch to normal mode' }) 104 | keymap.set('t', '', '', { desc = 'send Esc to terminal' }) 105 | 106 | -- Shortcut for expanding to current buffer's directory in command mode 107 | keymap.set('c', '%%', function() 108 | if fn.getcmdtype() == ':' then 109 | return fn.expand('%:h') .. '/' 110 | else 111 | return '%%' 112 | end 113 | end, { expr = true, desc = "expand to current buffer's directory" }) 114 | 115 | keymap.set('n', 'tn', vim.cmd.tabnew, { desc = '[t]ab new' }) 116 | keymap.set('n', 'tq', vim.cmd.tabclose, { desc = '[t]ab [q]uit' }) 117 | 118 | local severity = diagnostic.severity 119 | 120 | keymap.set('n', 'do', function() 121 | local _, winid = diagnostic.open_float(nil, { scope = 'line' }) 122 | vim.api.nvim_win_set_config(winid or 0, { focusable = true }) 123 | end, { noremap = true, silent = true, desc = '[d]iagnostics [o]pen floating' }) 124 | keymap.set('n', '[d', function() 125 | diagnostic.jump { count = -1, float = true } 126 | end, { noremap = true, silent = true, desc = 'previous [d]iagnostic' }) 127 | keymap.set('n', ']d', function() 128 | diagnostic.jump { count = 1, float = true } 129 | end, { noremap = true, silent = true, desc = 'next [d]iagnostic' }) 130 | keymap.set('n', '[e', function() 131 | diagnostic.jump { 132 | severity = severity.ERROR, 133 | count = -1, 134 | float = true, 135 | } 136 | end, { noremap = true, silent = true, desc = 'previous [e]rror' }) 137 | keymap.set('n', ']e', function() 138 | diagnostic.jump { 139 | severity = severity.ERROR, 140 | count = 1, 141 | float = true, 142 | } 143 | end, { noremap = true, silent = true, desc = 'next [e]rror' }) 144 | keymap.set('n', '[w', function() 145 | diagnostic.jump { 146 | severity = severity.WARN, 147 | count = -1, 148 | float = true, 149 | } 150 | end, { noremap = true, silent = true, desc = 'previous [w]arning' }) 151 | keymap.set('n', ']w', function() 152 | diagnostic.jump { 153 | severity = severity.WARN, 154 | count = 1, 155 | float = true, 156 | } 157 | end, { noremap = true, silent = true, desc = 'next [w]arning' }) 158 | keymap.set('n', 'dl', function() 159 | diagnostic.setloclist { open = false } 160 | end, { noremap = true, silent = true, desc = '[d]iagnostics to [l]oclist' }) 161 | keymap.set('n', 'dc', function() 162 | diagnostic.setqflist { open = false } 163 | end, { noremap = true, silent = true, desc = '[d]iagnostics to quickfix list [c]' }) 164 | keymap.set('n', 'ce', function() 165 | diagnostic.setqflist { open = false, severity = severity.ERROR } 166 | end, { noremap = true, silent = true, desc = '[c] [e]rror diagnostics to quickfix list' }) 167 | keymap.set('n', 'cw', function() 168 | diagnostic.setqflist { open = false, severity = severity.WARN } 169 | end, { noremap = true, silent = true, desc = '[c] [w]arning diagnostics to quickfix list' }) 170 | keymap.set('n', 'ci', function() 171 | diagnostic.setqflist { open = false, severity = severity.INFO } 172 | end, { noremap = true, silent = true, desc = '[c] [i]nfo diagnostics to quickfix list' }) 173 | keymap.set('n', 'ch', function() 174 | diagnostic.setqflist { open = false, severity = severity.HINT } 175 | end, { noremap = true, silent = true, desc = '[c] [h]int diagnostics to quickfix list' }) 176 | 177 | local function buf_toggle_diagnostics() 178 | local filter = { bufnr = api.nvim_get_current_buf() } 179 | diagnostic.enable(not diagnostic.is_enabled(filter), filter) 180 | end 181 | 182 | keymap.set('n', 'dt', buf_toggle_diagnostics) 183 | 184 | local function toggle_spell_check() 185 | ---@diagnostic disable-next-line: param-type-mismatch vim.opt.spell = not (vim.opt.spell:get()) 186 | end 187 | 188 | keymap.set('n', 'S', toggle_spell_check, { noremap = true, silent = true, desc = 'toggle [S]pell' }) 189 | 190 | keymap.set('n', '', 'zz', { desc = 'move down half-page and center' }) 191 | keymap.set('n', '', 'zz', { desc = 'move up half-page and center' }) 192 | keymap.set('n', '', 'zz', { desc = 'move down full-page and center' }) 193 | keymap.set('n', '', 'zz', { desc = 'move up full-page and center' }) 194 | keymap.set('n', '', 'zz', { desc = 'move up full-page and center' }) 195 | 196 | -- Terminal 197 | keymap.set('n', '', function() 198 | local term_bufnr = vim.iter(vim.api.nvim_list_bufs()):find(function(bufnr) 199 | if not vim.api.nvim_buf_is_loaded(bufnr) then 200 | return false 201 | end 202 | local name = vim.api.nvim_buf_get_name(bufnr) 203 | return vim.startswith(name, 'term://') 204 | end) 205 | if term_bufnr then 206 | vim.api.nvim_set_current_buf(term_bufnr) 207 | else 208 | vim.cmd.terminal() 209 | end 210 | end, { silent = true, noremap = true, desc = 'terminal' }) 211 | keymap.set('n', '', function() 212 | vim.cmd.vsplit() 213 | vim.cmd.terminal() 214 | end, { silent = true, noremap = true, desc = 'terminal [v]ertical split' }) 215 | keymap.set('n', '', function() 216 | vim.cmd.split() 217 | vim.cmd.terminal() 218 | end, { silent = true, noremap = true, desc = 'terminal [h]orizontal split' }) 219 | -------------------------------------------------------------------------------- /nvim/init.lua: -------------------------------------------------------------------------------- 1 | local cmd = vim.cmd 2 | local opt = vim.opt 3 | local keymap = vim.keymap 4 | local g = vim.g 5 | 6 | opt.compatible = false 7 | 8 | -- Disable mouse 9 | opt.mouse = '' 10 | 11 | -- Enable true colour support 12 | opt.termguicolors = true 13 | 14 | -- Search down into subfolders 15 | opt.path = vim.o.path .. '**' 16 | opt.number = true 17 | opt.relativenumber = true 18 | opt.cursorline = true 19 | opt.lazyredraw = true 20 | opt.showmatch = true -- Highlight matching parentheses, etc 21 | opt.incsearch = true 22 | opt.hlsearch = true 23 | 24 | opt.spell = true 25 | opt.spelllang = 'en,de_ch' 26 | 27 | -- On pressing tab, insert spaces 28 | opt.expandtab = true 29 | -- Show existing tab with 2 spaces width 30 | opt.tabstop = 2 31 | opt.softtabstop = 2 32 | -- When indenting with '>', use 2 spaces width 33 | opt.shiftwidth = 2 34 | opt.foldenable = true 35 | opt.foldlevelstart = 10 36 | -- opt.foldmethod = 'indent' -- fold based on indent level 37 | opt.history = 2000 38 | -- Increment numbers in decimal and hexadecimal formats 39 | opt.nrformats = 'bin,hex' -- 'octal' 40 | -- Persist undos between sessions 41 | opt.undofile = true 42 | -- Split right and below 43 | opt.splitright = true 44 | opt.splitbelow = true 45 | -- Global statusline 46 | -- opt.laststatus = 3 -- managed by lualine 47 | -- Hide command line unless typing a command or printing a message 48 | opt.cmdheight = 0 49 | 50 | -- Keep cursor in the middle of the pane while scrolling 51 | -- opt.scrolloff = 10000 52 | 53 | opt.fillchars = [[eob: ,fold: ,foldopen:,foldsep: ,foldclose:]] 54 | 55 | opt.completeopt = 'menu,menuone,noinsert,fuzzy,popup,noselect' 56 | 57 | g.markdown_syntax_conceal = 0 58 | 59 | -- See https://github.com/hrsh7th/nvim-compe/issues/286#issuecomment-805140394 60 | g.omni_sql_default_compl_type = 'syntax' 61 | 62 | -- Set default shell 63 | if vim.fn.executable('nu') == 1 then 64 | opt.shell = 'nu' 65 | elseif vim.fn.executable('zsh') == 1 then 66 | opt.shell = 'zsh' 67 | end 68 | 69 | opt.timeout = true 70 | opt.timeoutlen = 300 71 | 72 | opt.tags:append { '.tags' } 73 | 74 | local function format_diagnostic(prefix, diagnostic) 75 | local formatted_message = diagnostic 76 | .message 77 | -- Replace any sequence of whitespace characters (including newlines) with a single space 78 | :gsub('%s+', ' ') 79 | return string.format(prefix .. ' %s', formatted_message) 80 | end 81 | 82 | local virtual_text_config = { 83 | prefix = '', 84 | format = function(diagnostic) 85 | local severity = diagnostic.severity 86 | if severity == vim.diagnostic.severity.ERROR then 87 | return format_diagnostic('󰅚', diagnostic) 88 | end 89 | if severity == vim.diagnostic.severity.WARN then 90 | return format_diagnostic('⚠', diagnostic) 91 | end 92 | if severity == vim.diagnostic.severity.INFO then 93 | return format_diagnostic('ⓘ', diagnostic) 94 | end 95 | if severity == vim.diagnostic.severity.HINT then 96 | return format_diagnostic('󰌶', diagnostic) 97 | end 98 | return format_diagnostic('■', diagnostic) 99 | end, 100 | } 101 | 102 | local diagnostic_config = { 103 | virtual_text = virtual_text_config, 104 | signs = { 105 | text = { 106 | [vim.diagnostic.severity.ERROR] = '󰅚', 107 | [vim.diagnostic.severity.WARN] = '⚠', 108 | [vim.diagnostic.severity.INFO] = 'ⓘ', 109 | [vim.diagnostic.severity.HINT] = '󰌶', 110 | }, 111 | }, 112 | update_in_insert = false, 113 | underline = true, 114 | severity_sort = true, 115 | float = { 116 | focusable = false, 117 | style = 'minimal', 118 | border = 'rounded', 119 | source = 'if_many', 120 | header = '', 121 | prefix = '', 122 | }, 123 | } 124 | 125 | vim.diagnostic.config(diagnostic_config) 126 | 127 | ---@param direction 'forward' | 'backward' 128 | local function cycle_diagnostic_modes(direction) 129 | local current_config = vim.diagnostic.config() or diagnostic_config 130 | local modes = { 131 | { virtual_text = virtual_text_config, virtual_lines = false }, 132 | { virtual_text = false, virtual_lines = true }, 133 | { virtual_text = false, virtual_lines = false }, 134 | } 135 | 136 | local current_mode_index 137 | for i, mode in ipairs(modes) do 138 | if 139 | ( 140 | (type(current_config.virtual_text) == 'table' and mode.virtual_text == virtual_text_config) 141 | or (current_config.virtual_text == mode.virtual_text) 142 | ) and (current_config.virtual_lines == mode.virtual_lines) 143 | then 144 | current_mode_index = i 145 | break 146 | end 147 | end 148 | local next_mode_index 149 | if direction == 'forward' then 150 | next_mode_index = (current_mode_index % #modes) + 1 151 | else 152 | next_mode_index = (current_mode_index - 2 + #modes) % #modes + 1 153 | end 154 | vim.diagnostic.config(vim.tbl_extend('force', current_config, modes[next_mode_index])) 155 | end 156 | 157 | vim.keymap.set('n', 'd]', function() 158 | cycle_diagnostic_modes('forward') 159 | end, { noremap = true, silent = true }) 160 | 161 | vim.keymap.set('n', 'd[', function() 162 | cycle_diagnostic_modes('backward') 163 | end, { noremap = true, silent = true }) 164 | 165 | vim.api.nvim_create_autocmd('BufEnter', { 166 | group = vim.api.nvim_create_augroup('DisableNewLineAutoCommentString', {}), 167 | callback = function() 168 | vim.opt.formatoptions = vim.opt.formatoptions - { 'c', 'r', 'o' } 169 | end, 170 | }) 171 | 172 | g.editorconfig = true 173 | 174 | vim.opt.colorcolumn = '100' 175 | 176 | -- Native plugins 177 | cmd.filetype('plugin', 'indent', 'on') 178 | cmd.packadd('cfilter') 179 | 180 | -- Disable builtin plugins 181 | g.loaded_gzip = 1 182 | g.loaded_zip = 1 183 | g.loaded_zipPlugin = 1 184 | g.loaded_tar = 1 185 | g.loaded_tarPlugin = 1 186 | 187 | g.loaded_getscript = 1 188 | g.loaded_getscriptPlugin = 1 189 | g.loaded_vimball = 1 190 | g.loaded_vimballPlugin = 1 191 | g.loaded_2html_plugin = 1 192 | 193 | g.loaded_matchit = 1 194 | g.loaded_matchparen = 1 195 | g.loaded_logiPat = 1 196 | g.loaded_rrhelper = 1 197 | 198 | g.loaded_netrw = 1 199 | g.loaded_netrwPlugin = 1 200 | g.loaded_netrwSettings = 1 201 | g.loaded_netrwFileHandlers = 1 202 | 203 | vim.cmd([[ 204 | set sessionoptions-=buffers 205 | ]]) 206 | 207 | -- Plugin settings 208 | local keymap_opts = { noremap = true, silent = true } 209 | 210 | ---@return haskell-tools.Opts 211 | g.haskell_tools = function() 212 | ---@type haskell-tools.Opts 213 | local ht_opts = { 214 | tools = { 215 | repl = { 216 | auto_focus = false, 217 | }, 218 | codeLens = { 219 | autoRefresh = false, 220 | }, 221 | definition = { 222 | hoogle_signature_fallback = true, 223 | }, 224 | }, 225 | hls = { 226 | -- for hls development 227 | -- cmd = { 'cabal', 'run', 'haskell-language-server' }, 228 | on_attach = function(_, bufnr, ht) 229 | local desc = function(description) 230 | return vim.tbl_extend('keep', keymap_opts, { buffer = bufnr, desc = description }) 231 | end 232 | keymap.set('n', 'gh', ht.hoogle.hoogle_signature, desc('haskell: [h]oogle signature search')) 233 | keymap.set('n', 'tg', function() 234 | vim.cmd.Telescope { 'ht', 'package_grep' } 235 | end, desc('haskell: [t]elescope package [g]rep')) 236 | keymap.set('n', 'th', function() 237 | vim.cmd.Telescope { 'ht', 'package_hsgrep' } 238 | end, desc('haskell: [t]elescope package grep [h]askell files')) 239 | keymap.set('n', 'tf', function() 240 | vim.cmd.Telescope { 'ht', 'package_files' } 241 | end, desc('haskell: [t]elescope package [f]iles')) 242 | keymap.set('n', 'ea', ht.lsp.buf_eval_all, desc('haskell: [e]valuate [a]ll')) 243 | end, 244 | default_settings = { 245 | haskell = { 246 | checkProject = false, -- PERF: don't check the entire project on initial load 247 | formattingProvider = 'fourmolu', 248 | maxCompletions = 30, 249 | plugin = { 250 | semanticTokens = { 251 | globalOn = true, 252 | }, 253 | rename = { 254 | config = { 255 | diff = true, -- (experimental) rename across modules 256 | }, 257 | }, 258 | }, 259 | }, 260 | }, 261 | }, 262 | } 263 | return ht_opts 264 | end 265 | 266 | ---@return rustaceanvim.Opts 267 | g.rustaceanvim = function() 268 | ---@type rustaceanvim.Opts 269 | local rustacean_opts = { 270 | server = { 271 | default_settings = { 272 | ['rust-analyzer'] = { 273 | cargo = { 274 | loadOutDirsFromCheck = true, 275 | runBuildScripts = true, 276 | }, 277 | procMacro = { 278 | enable = true, 279 | }, 280 | inlayHints = { 281 | lifetimeElisionHints = { 282 | enable = true, 283 | useParameterNames = true, 284 | }, 285 | }, 286 | }, 287 | }, 288 | }, 289 | } 290 | return rustacean_opts 291 | end 292 | 293 | -- nvim-ts-context-commentstring 294 | g.skip_ts_context_commentstring_module = true 295 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Neovim config"; 3 | 4 | nixConfig = { 5 | extra-substituters = "https://mrcjkb.cachix.org"; 6 | extra-trusted-public-keys = "mrcjkb.cachix.org-1:KhpstvH5GfsuEFOSyGjSTjng8oDecEds7rbrI96tjA4="; 7 | }; 8 | 9 | inputs = { 10 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 11 | neovim-nightly.url = "github:nix-community/neovim-nightly-overlay"; 12 | neorocks = { 13 | url = "github:nvim-neorocks/neorocks"; 14 | }; 15 | spell-de-dictionary = { 16 | url = "https://www.mirrorservice.org/pub/vim/runtime/spell/de.utf-8.spl"; 17 | flake = false; 18 | }; 19 | spell-de-suggestions = { 20 | url = "https://www.mirrorservice.org/pub/vim/runtime/spell/de.utf-8.sug"; 21 | flake = false; 22 | }; 23 | gen-luarc.url = "github:mrcjkb/nix-gen-luarc-json"; 24 | git-hooks = { 25 | url = "github:cachix/git-hooks.nix"; 26 | inputs.nixpkgs.follows = "nixpkgs"; 27 | }; 28 | flake-utils.url = "github:numtide/flake-utils"; 29 | 30 | # Plugins 31 | haskell-tools = { 32 | url = "github:mrcjkb/haskell-tools.nvim"; 33 | # url = "/home/mrcjk/.local/share/nvim/site/pack/dev/opt/haskell-tools.nvim"; 34 | }; 35 | neotest-haskell = { 36 | url = "github:mrcjkb/neotest-haskell"; 37 | }; 38 | rustaceanvim = { 39 | url = "github:mrcjkb/rustaceanvim"; 40 | }; 41 | lz-n = { 42 | url = "github:nvim-neorocks/lz.n"; 43 | # url = "/home/mrcjk/git/github/mrcjkb/lz.n"; 44 | }; 45 | fff-nvim.url = "github:dmtrKovalenko/fff.nvim"; 46 | crates-nvim = { 47 | url = "github:saecki/crates.nvim"; 48 | flake = false; 49 | }; 50 | plenary = { 51 | url = "github:nvim-lua/plenary.nvim"; 52 | flake = false; 53 | }; 54 | sqlite = { 55 | url = "github:kkharji/sqlite.lua"; 56 | flake = false; 57 | }; 58 | nvim-web-devicons = { 59 | url = "github:nvim-tree/nvim-web-devicons"; 60 | flake = false; 61 | }; 62 | vim-wordmotion = { 63 | # Vimscript 64 | url = "github:chaoren/vim-wordmotion"; 65 | flake = false; 66 | }; 67 | nvim-highlight-colors = { 68 | url = "github:brenoprata10/nvim-highlight-colors"; 69 | flake = false; 70 | }; 71 | flash-nvim = { 72 | url = "github:folke/flash.nvim"; 73 | flake = false; 74 | }; 75 | eyeliner-nvim = { 76 | url = "github:jinh0/eyeliner.nvim"; 77 | flake = false; 78 | }; 79 | gitlinker = { 80 | url = "github:linrongbin16/gitlinker.nvim"; 81 | flake = false; 82 | }; 83 | surround = { 84 | url = "github:kylechui/nvim-surround"; 85 | flake = false; 86 | }; 87 | substitute = { 88 | url = "github:gbprod/substitute.nvim"; 89 | flake = false; 90 | }; 91 | persistence = { 92 | url = "github:folke/persistence.nvim"; 93 | flake = false; 94 | }; 95 | nvim-lastplace.url = "github:mrcjkb/nvim-lastplace"; 96 | comment = { 97 | url = "github:numToStr/Comment.nvim"; 98 | flake = false; 99 | }; 100 | neotest = { 101 | url = "github:nvim-neotest/neotest"; 102 | # url = "github:mrcjkb/neotest/watcher"; 103 | # url = "/home/mrcjk/git/github/forks/nvim/neotest/"; 104 | flake = false; 105 | }; 106 | nio = { 107 | url = "github:nvim-neotest/nvim-nio"; 108 | flake = false; 109 | }; 110 | neotest-java = { 111 | url = "github:rcasia/neotest-java"; 112 | flake = false; 113 | }; 114 | neotest-busted = { 115 | url = "gitlab:HiPhish/neotest-busted"; 116 | flake = false; 117 | }; 118 | schemastore-nvim = { 119 | url = "github:b0o/SchemaStore.nvim"; 120 | flake = false; 121 | }; 122 | jdtls = { 123 | # FIXME: Update setup in dotfiles 124 | url = "github:mfussenegger/nvim-jdtls"; 125 | flake = false; 126 | }; 127 | live-rename-nvim = { 128 | url = "github:saecki/live-rename.nvim"; 129 | flake = false; 130 | }; 131 | fidget = { 132 | url = "git+https://github.com/j-hui/fidget.nvim.git"; 133 | flake = false; 134 | }; 135 | illuminate = { 136 | url = "github:RRethy/vim-illuminate"; 137 | flake = false; 138 | }; 139 | actions-preview-nvim = { 140 | url = "github:aznhe21/actions-preview.nvim"; 141 | flake = false; 142 | }; 143 | treesitter-textobjects = { 144 | url = "github:nvim-treesitter/nvim-treesitter-textobjects/main"; 145 | flake = false; 146 | }; 147 | treesitter-context = { 148 | url = "github:nvim-treesitter/nvim-treesitter-context"; 149 | flake = false; 150 | }; 151 | nvim-ts-context-commentstring = { 152 | url = "github:JoosepAlviste/nvim-ts-context-commentstring"; 153 | flake = false; 154 | }; 155 | rainbow-delimiters-nvim = { 156 | url = "github:hiphish/rainbow-delimiters.nvim"; 157 | flake = false; 158 | }; 159 | vim-matchup = { 160 | # tree-sitter powered % motions 161 | url = "github:andymass/vim-matchup"; 162 | flake = false; 163 | }; 164 | telescope = { 165 | url = "github:nvim-telescope/telescope.nvim/0.1.x"; 166 | flake = false; 167 | }; 168 | telescope_hoogle = { 169 | url = "github:luc-tielen/telescope_hoogle"; 170 | flake = false; 171 | }; 172 | telescope-smart-history = { 173 | url = "github:nvim-telescope/telescope-smart-history.nvim"; 174 | flake = false; 175 | }; 176 | todo-comments = { 177 | url = "github:folke/todo-comments.nvim"; 178 | flake = false; 179 | }; 180 | lualine = { 181 | url = "github:hoob3rt/lualine.nvim"; 182 | flake = false; 183 | }; 184 | oil-nvim = { 185 | url = "github:stevearc/oil.nvim"; 186 | flake = false; 187 | }; 188 | harpoon = { 189 | url = "github:ThePrimeagen/harpoon/harpoon2"; 190 | flake = false; 191 | }; 192 | gitsigns = { 193 | url = "github:lewis6991/gitsigns.nvim"; 194 | flake = false; 195 | }; 196 | nvim-bqf = { 197 | url = "github:kevinhwang91/nvim-bqf"; 198 | flake = false; 199 | }; 200 | quicker-nvim = { 201 | url = "github:stevearc/quicker.nvim"; 202 | flake = false; 203 | }; 204 | yanky = { 205 | url = "github:gbprod/yanky.nvim"; 206 | flake = false; 207 | }; 208 | statuscol = { 209 | url = "github:luukvbaal/statuscol.nvim"; 210 | flake = false; 211 | }; 212 | nvim-unception = { 213 | # Prevent nested neovim instances 214 | url = "github:samjwill/nvim-unception"; 215 | flake = false; 216 | }; 217 | term-edit-nvim = { 218 | url = "github:chomosuke/term-edit.nvim"; 219 | flake = false; 220 | }; 221 | other-nvim = { 222 | url = "github:rgroli/other.nvim"; 223 | flake = false; 224 | }; 225 | which-key-nvim = { 226 | url = "github:folke/which-key.nvim"; 227 | flake = false; 228 | }; 229 | snacks-nvim = { 230 | url = "github:folke/snacks.nvim"; 231 | flake = false; 232 | }; 233 | }; 234 | 235 | outputs = inputs @ { 236 | self, 237 | nixpkgs, 238 | neovim-nightly, 239 | neorocks, 240 | gen-luarc, 241 | flake-utils, 242 | git-hooks, 243 | ... 244 | }: let 245 | supportedSystems = [ 246 | "aarch64-linux" 247 | "x86_64-linux" 248 | "aarch64-darwin" 249 | "x86_64-darwin" 250 | ]; 251 | 252 | plugin-overlay = import ./nix/plugin-overlay.nix {inherit inputs;}; 253 | neovim-overlay = import ./nix/neovim-overlay.nix {inherit inputs;}; 254 | in 255 | flake-utils.lib.eachSystem supportedSystems (system: let 256 | pkgs = import nixpkgs { 257 | inherit system; 258 | overlays = [ 259 | neorocks.overlays.default 260 | neovim-nightly.overlays.default 261 | gen-luarc.overlays.default 262 | plugin-overlay 263 | neovim-overlay 264 | ]; 265 | }; 266 | shell = pkgs.mkShell { 267 | name = "nvim-devShell"; 268 | buildInputs = 269 | self.checks.${system}.git-hooks-check.enabledPackages 270 | ++ (with pkgs; [ 271 | lua-language-server 272 | # emmylua-ls 273 | nil 274 | ]); 275 | shellHook = '' 276 | ${self.checks.${system}.git-hooks-check.shellHook} 277 | ln -fs ${pkgs.luarc-json} .luarc.json 278 | ''; 279 | }; 280 | git-hooks-check = git-hooks.lib.${system}.run { 281 | src = self; 282 | hooks = { 283 | alejandra.enable = true; 284 | stylua.enable = true; 285 | luacheck.enable = true; 286 | }; 287 | }; 288 | in { 289 | packages = rec { 290 | default = nvim; 291 | nvim = pkgs.nvim-pkg; 292 | nvim-dev = pkgs.nvim-dev; 293 | nvim-profile = pkgs.nvim-profile; 294 | nightly = pkgs.neovim-nightly; 295 | }; 296 | devShells = { 297 | default = shell; 298 | }; 299 | checks = { 300 | inherit git-hooks-check; 301 | }; 302 | }) 303 | // { 304 | overlays.default = neovim-overlay; 305 | }; 306 | } 307 | -------------------------------------------------------------------------------- /nix/neovim-overlay.nix: -------------------------------------------------------------------------------- 1 | {inputs}: final: prev: 2 | with final.lib; let 3 | system = final.stdenv.hostPlatform.system; 4 | mkNeovim = { 5 | appName ? null, 6 | plugins ? [], 7 | devPlugins ? [], 8 | extraPackages ? [], 9 | resolvedExtraLuaPackages ? [], 10 | extraPython3Packages ? p: [], 11 | withPython3 ? true, 12 | withRuby ? false, 13 | withNodeJs ? false, 14 | viAlias ? true, 15 | vimAlias ? true, 16 | initLuaPre ? "", 17 | }: let 18 | defaultPlugin = { 19 | plugin = null; 20 | config = null; 21 | optional = false; 22 | runtime = {}; 23 | }; 24 | 25 | externalPackages = 26 | extraPackages 27 | ++ (with final; [ 28 | sqlite 29 | nodePackages.vscode-json-languageserver 30 | ]); 31 | 32 | normalizedPlugins = map (x: 33 | defaultPlugin 34 | // ( 35 | if x ? plugin 36 | then x 37 | else {plugin = x;} 38 | )) 39 | plugins; 40 | 41 | neovimConfig = final.neovimUtils.makeNeovimConfig { 42 | inherit extraPython3Packages withPython3 withRuby withNodeJs viAlias vimAlias; 43 | plugins = normalizedPlugins; 44 | }; 45 | 46 | nvimConfig = final.stdenv.mkDerivation { 47 | name = "nvim-config"; 48 | src = ../nvim; 49 | 50 | buildPhase = '' 51 | mkdir -p $out/nvim 52 | rm init.lua 53 | ''; 54 | 55 | installPhase = '' 56 | cp -r * $out/nvim 57 | rm -r $out/nvim/after 58 | cp -r after $out/after 59 | ln -s ${inputs.spell-de-dictionary} $out/nvim/spell/de.utf-8.spl; 60 | ln -s ${inputs.spell-de-suggestions} $out/nvim/spell/de.utf-8.sug; 61 | ''; 62 | }; 63 | 64 | initLua = 65 | initLuaPre 66 | + "" 67 | + 68 | /* 69 | lua 70 | */ 71 | '' 72 | vim.loader.enable() 73 | vim.opt.rtp:prepend('${../lib}') 74 | '' 75 | + "" 76 | + (builtins.readFile ../nvim/init.lua) 77 | + "" 78 | + optionalString (devPlugins != []) ( 79 | /* 80 | lua 81 | */ 82 | '' 83 | local dev_pack_path = vim.fn.stdpath('data') .. '/site/pack/dev' 84 | local dev_plugins_dir = dev_pack_path .. '/opt' 85 | local dev_plugin_path 86 | '' 87 | + strings.concatMapStringsSep 88 | "\n" 89 | (plugin: 90 | /* 91 | lua 92 | */ 93 | '' 94 | dev_plugin_path = dev_plugins_dir .. '/${plugin.name}' 95 | if vim.fn.empty(vim.fn.glob(dev_plugin_path)) > 0 then 96 | vim.notify('Bootstrapping dev plugin ${plugin.name} ...', vim.log.levels.INFO) 97 | vim.cmd('!${final.git}/bin/git clone ${plugin.url} ' .. dev_plugin_path) 98 | end 99 | vim.cmd('packadd! ${plugin.name}') 100 | '') 101 | devPlugins 102 | ) 103 | + 104 | /* 105 | lua 106 | */ 107 | '' 108 | vim.opt.rtp:append('${nvimConfig}/nvim') 109 | vim.opt.rtp:append('${nvimConfig}/after') 110 | ''; 111 | 112 | libExt = final.stdenv.hostPlatform.extensions.sharedLibrary; 113 | 114 | extraMakeWrapperArgs = builtins.concatStringsSep " " ( 115 | (optional (appName != "nvim" && appName != null && appName != "") 116 | ''--set NVIM_APPNAME "${appName}"'') 117 | ++ (optional (externalPackages != []) 118 | ''--prefix PATH : "${makeBinPath externalPackages}"'') 119 | ++ [ 120 | ''--set LIBSQLITE_CLIB_PATH "${final.sqlite.out}/lib/libsqlite3${libExt}"'' 121 | ''--set LIBSQLITE "${final.sqlite.out}/lib/libsqlite3${libExt}"'' 122 | ] 123 | ); 124 | 125 | extraMakeWrapperLuaCArgs = optionalString (resolvedExtraLuaPackages != []) '' 126 | --suffix LUA_CPATH ";" "${ 127 | lib.concatMapStringsSep ";" final.luaPackages.getLuaCPath 128 | resolvedExtraLuaPackages 129 | }"''; 130 | 131 | extraMakeWrapperLuaArgs = 132 | optionalString (resolvedExtraLuaPackages != []) 133 | '' 134 | --suffix LUA_PATH ";" "${ 135 | concatMapStringsSep ";" final.luaPackages.getLuaPath 136 | resolvedExtraLuaPackages 137 | }"''; 138 | 139 | excludeFiles = [ 140 | "indent.vim" 141 | "menu.vim" 142 | "mswin.vim" 143 | "plugin/matchit.vim" 144 | "plugin/matchparen.vim" 145 | "plugin/rplugin.vim" 146 | "plugin/shada.vim" 147 | "plugin/tohtml.vim" 148 | "plugin/tutor.vim" 149 | "plugin/gzip.vim" 150 | "plugin/tarPlugin.vim" 151 | "plugin/zipPlugin.vim" 152 | ]; 153 | postInstallCommands = map (target: "rm -f $out/share/nvim/runtime/${target}") excludeFiles; 154 | 155 | nvim-unwrapped = prev.neovim.overrideAttrs (oa: { 156 | postInstall = '' 157 | ${oa.postInstall or ""} 158 | ${concatStringsSep "\n" postInstallCommands} 159 | ''; 160 | }); 161 | in 162 | final.wrapNeovimUnstable nvim-unwrapped (neovimConfig 163 | // { 164 | luaRcContent = initLua; 165 | wrapperArgs = 166 | escapeShellArgs neovimConfig.wrapperArgs 167 | + " " 168 | + extraMakeWrapperArgs 169 | + " " 170 | + extraMakeWrapperLuaCArgs 171 | + " " 172 | + extraMakeWrapperLuaArgs; 173 | wrapRc = true; 174 | }); 175 | 176 | opt = drv: { 177 | plugin = drv; 178 | optional = true; 179 | }; 180 | 181 | base-plugins = with final.nvimPlugins; 182 | [ 183 | plenary 184 | sqlite 185 | nvim-web-devicons 186 | nvim-ts-context-commentstring 187 | treesitter-textobjects 188 | treesitter-context 189 | rainbow-delimiters-nvim 190 | (opt vim-matchup) 191 | vim-wordmotion 192 | nvim-highlight-colors 193 | flash-nvim 194 | eyeliner-nvim 195 | gitlinker 196 | surround 197 | substitute 198 | persistence 199 | nvim-lastplace 200 | comment 201 | crates-nvim 202 | (opt neotest) 203 | (opt neotest-java) 204 | neotest-busted 205 | nio # TODO: Remove when rocks-dev is ready 206 | jdtls 207 | live-rename-nvim 208 | fidget 209 | (opt illuminate) 210 | schemastore-nvim 211 | actions-preview-nvim 212 | telescope_hoogle 213 | telescope-smart-history 214 | (opt telescope) 215 | fff-nvim 216 | todo-comments 217 | lualine 218 | (opt harpoon) 219 | gitsigns 220 | nvim-bqf 221 | quicker-nvim 222 | yanky 223 | statuscol 224 | nvim-unception 225 | term-edit-nvim 226 | oil-nvim 227 | other-nvim 228 | which-key-nvim 229 | ] 230 | ++ (with prev.vimPlugins; [ 231 | telescope-fzy-native-nvim 232 | (opt dial-nvim) 233 | vim-scriptease 234 | catppuccin-nvim 235 | (opt (nvim-treesitter.withPlugins (ps: 236 | with ps; [ 237 | bash 238 | c 239 | cpp 240 | css 241 | dhall 242 | diff 243 | dockerfile 244 | editorconfig 245 | gitcommit 246 | graphql 247 | haskell 248 | haskell_persistent 249 | html 250 | java 251 | jq 252 | json 253 | json5 254 | latex 255 | lua 256 | luadoc 257 | make 258 | markdown 259 | markdown_inline 260 | mermaid 261 | nix 262 | nu 263 | proto 264 | python 265 | regex 266 | rust 267 | scala 268 | scheme 269 | sql 270 | terraform 271 | thrift 272 | toml 273 | typescript 274 | typst 275 | vim 276 | vimdoc 277 | yaml 278 | ]))) 279 | ]); 280 | 281 | all-plugins = 282 | base-plugins 283 | ++ [ 284 | inputs.haskell-tools.packages.${system}.default 285 | inputs.neotest-haskell.packages.${system}.default 286 | inputs.rustaceanvim.packages.${system}.default 287 | inputs.lz-n.packages.${system}.default 288 | ]; 289 | 290 | extraPackages = with final; [ 291 | haskellPackages.fast-tags 292 | nodePackages.vim-language-server 293 | nodePackages.yaml-language-server 294 | # nodePackages.vscode-langservers-extracted 295 | nodePackages.bash-language-server 296 | dockerfile-language-server 297 | taplo # toml toolkit including a language server 298 | sqls 299 | gitu 300 | jujutsu 301 | ]; 302 | 303 | nvim-dev = mkNeovim { 304 | plugins = base-plugins; 305 | devPlugins = [ 306 | { 307 | name = "haskell-tools.nvim"; 308 | url = "git@github.com:mrcjkb/haskell-tools.nvim.git"; 309 | } 310 | { 311 | name = "neotest-haskell"; 312 | url = "git@github.com:mrcjkb/neotest-haskell.git"; 313 | } 314 | { 315 | name = "rustaceanvim"; 316 | url = "git@github.com:mrcjkb/rustaceanvim.git"; 317 | } 318 | { 319 | name = "lz.n"; 320 | url = "git@github.com:nvim-neorocks/lz.n.git"; 321 | } 322 | { 323 | name = "lsp-workspace.nvim"; 324 | url = "git@github.com:mrcjkb/lsp-workspace.nvim.git"; 325 | } 326 | ]; 327 | inherit extraPackages; 328 | }; 329 | 330 | nvim-pkg = mkNeovim { 331 | plugins = all-plugins; 332 | inherit extraPackages; 333 | }; 334 | 335 | nvim-profile = mkNeovim { 336 | plugins = 337 | all-plugins 338 | ++ (with final.nvimPlugins; [ 339 | snacks-nvim 340 | ]); 341 | inherit extraPackages; 342 | initLuaPre = 343 | /* 344 | lua 345 | */ 346 | '' 347 | require('snacks.profiler').startup {} 348 | ''; 349 | }; 350 | 351 | luarc-json = final.mk-luarc-json { 352 | plugins = all-plugins; 353 | nvim = final.neovim; 354 | }; 355 | in { 356 | inherit 357 | nvim-dev 358 | nvim-pkg 359 | nvim-profile 360 | luarc-json 361 | ; 362 | } 363 | -------------------------------------------------------------------------------- /lib/lua/mrcjk/indentexpr.lua: -------------------------------------------------------------------------------- 1 | local ts = vim.treesitter 2 | 3 | local internal = {} 4 | 5 | internal.avoid_force_reparsing = { 6 | yaml = true, 7 | } 8 | 9 | internal.comment_parsers = { 10 | comment = true, 11 | jsdoc = true, 12 | phpdoc = true, 13 | } 14 | 15 | local function getline(lnum) 16 | return vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] or '' 17 | end 18 | 19 | ---@param root TSNode 20 | ---@param lnum integer 21 | ---@param col? integer 22 | ---@return TSNode? 23 | local function get_first_node_at_line(root, lnum, col) 24 | col = col or vim.fn.indent(lnum) 25 | return root:descendant_for_range(lnum - 1, col, lnum - 1, col + 1) 26 | end 27 | 28 | ---@param root TSNode 29 | ---@param lnum integer 30 | ---@param col? integer 31 | ---@return TSNode? 32 | local function get_last_node_at_line(root, lnum, col) 33 | col = col or (#getline(lnum) - 1) 34 | return root:descendant_for_range(lnum - 1, col, lnum - 1, col + 1) 35 | end 36 | 37 | ---@param node TSNode 38 | ---@return number 39 | local function node_length(node) 40 | local _, _, start_byte = node:start() 41 | local _, _, end_byte = node:end_() 42 | return end_byte - start_byte 43 | end 44 | 45 | ---@param bufnr integer 46 | ---@param node TSNode 47 | ---@param delimiter string 48 | ---@return TSNode|nil child 49 | ---@return boolean|nil is_end 50 | local function find_delimiter(bufnr, node, delimiter) 51 | for child, _ in node:iter_children() do 52 | if child:type() == delimiter then 53 | local linenr = child:start() 54 | local line = vim.api.nvim_buf_get_lines(bufnr, linenr, linenr + 1, false)[1] 55 | local end_char = { child:end_() } 56 | local trimmed_after_delim 57 | local escaped_delimiter = delimiter:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]', '%%%1') 58 | trimmed_after_delim = line:sub(end_char[2] + 1):gsub('[%s' .. escaped_delimiter .. ']*', '') 59 | return child, #trimmed_after_delim == 0 60 | end 61 | end 62 | end 63 | 64 | ---Memoize a function using hash_fn to hash the arguments. 65 | ---@generic F: function 66 | ---@param fn F 67 | ---@param hash_fn fun(...): any 68 | ---@return F 69 | local function memoize(fn, hash_fn) 70 | local cache = setmetatable({}, { __mode = 'kv' }) ---@type table 71 | 72 | return function(...) 73 | local key = hash_fn(...) 74 | if cache[key] == nil then 75 | local v = fn(...) ---@type any 76 | cache[key] = v ~= nil and v or vim.NIL 77 | end 78 | 79 | local v = cache[key] 80 | return v ~= vim.NIL and v or nil 81 | end 82 | end 83 | 84 | local get_indents = memoize(function(bufnr, root, lang) 85 | local map = { 86 | ['indent.auto'] = {}, 87 | ['indent.begin'] = {}, 88 | ['indent.end'] = {}, 89 | ['indent.dedent'] = {}, 90 | ['indent.branch'] = {}, 91 | ['indent.ignore'] = {}, 92 | ['indent.align'] = {}, 93 | ['indent.zero'] = {}, 94 | } 95 | 96 | local query = ts.query.get(lang, 'indents') 97 | if not query then 98 | return map 99 | end 100 | for id, node, metadata in query:iter_captures(root, bufnr) do 101 | if query.captures[id]:sub(1, 1) ~= '_' then 102 | map[query.captures[id]][node:id()] = metadata or {} 103 | end 104 | end 105 | 106 | return map 107 | end, function(bufnr, root, lang) 108 | return tostring(bufnr) .. root:id() .. '_' .. lang 109 | end) 110 | 111 | ---@param lnum number (1-indexed) 112 | ---@return integer 113 | function internal.get_indent(lnum) 114 | local bufnr = vim.api.nvim_get_current_buf() 115 | local parser = ts.get_parser(bufnr) 116 | if not parser or not lnum then 117 | return -1 118 | end 119 | 120 | local ft = vim.bo[bufnr].filetype 121 | local root_lang = vim.treesitter.language.get_lang(ft) or ft 122 | 123 | -- some languages like Python will actually have worse results when re-parsing at opened new line 124 | if not internal.avoid_force_reparsing[root_lang] then 125 | -- Reparse in case we got triggered by ":h indentkeys" 126 | parser:parse { vim.fn.line('w0') - 1, vim.fn.line('w$') } 127 | end 128 | 129 | -- Get language tree with smallest range around node that's not a comment parser 130 | local root, lang_tree ---@type TSNode 131 | parser:for_each_tree(function(tstree, tree) 132 | if not tstree or internal.comment_parsers[tree:lang()] then 133 | return 134 | end 135 | local local_root = tstree:root() 136 | if ts.is_in_node_range(local_root, lnum - 1, 0) then 137 | if not root or node_length(root) >= node_length(local_root) then 138 | root = local_root 139 | lang_tree = tree 140 | end 141 | end 142 | end) 143 | 144 | -- Not likely, but just in case... 145 | if not root then 146 | return 0 147 | end 148 | 149 | local q = get_indents(vim.api.nvim_get_current_buf(), root, lang_tree:lang()) 150 | local node ---@type TSNode? 151 | if getline(lnum):find('^%s*$') then 152 | local prevlnum = vim.fn.prevnonblank(lnum) 153 | local indent = vim.fn.indent(prevlnum) 154 | local prevline = vim.trim(getline(prevlnum)) 155 | -- The final position can be trailing spaces, which should not affect indentation 156 | node = get_last_node_at_line(root, prevlnum, indent + #prevline - 1) 157 | if node and node:type():find('comment') then 158 | -- The final node we capture of the previous line can be a comment node, which should also be ignored 159 | -- Unless the last line is an entire line of comment, ignore the comment range and find the last node again 160 | local first_node = get_first_node_at_line(root, prevlnum, indent) 161 | local _, scol, _, _ = node:range() 162 | if first_node and first_node:id() ~= node:id() then 163 | -- In case the last captured node is a trailing comment node, re-trim the string 164 | prevline = vim.trim(prevline:sub(1, scol - indent)) 165 | -- Add back indent as indent of prevline was trimmed away 166 | local col = indent + #prevline - 1 167 | node = get_last_node_at_line(root, prevlnum, col) 168 | end 169 | end 170 | if node and q['indent.end'][node:id()] then 171 | node = get_first_node_at_line(root, lnum) 172 | end 173 | else 174 | node = get_first_node_at_line(root, lnum) 175 | end 176 | 177 | local indent_size = vim.fn.shiftwidth() 178 | local indent = 0 179 | local _, _, root_start = root:start() 180 | if root_start ~= 0 then 181 | -- injected tree 182 | indent = vim.fn.indent(root:start() + 1) 183 | end 184 | 185 | -- tracks to ensure multiple indent levels are not applied for same line 186 | local is_processed_by_row = {} --- @type table 187 | 188 | if node and q['indent.zero'][node:id()] then 189 | return 0 190 | end 191 | 192 | while node do 193 | -- do 'autoindent' if not marked as @indent 194 | if 195 | not q['indent.begin'][node:id()] 196 | and not q['indent.align'][node:id()] 197 | and q['indent.auto'][node:id()] 198 | and node:start() < lnum - 1 199 | and lnum - 1 <= node:end_() 200 | then 201 | return -1 202 | end 203 | 204 | -- Do not indent if we are inside an @ignore block. 205 | -- If a node spans from L1,C1 to L2,C2, we know that lines where L1 < line <= L2 would 206 | -- have their indentations contained by the node. 207 | if 208 | not q['indent.begin'][node:id()] 209 | and q['indent.ignore'][node:id()] 210 | and node:start() < lnum - 1 211 | and lnum - 1 <= node:end_() 212 | then 213 | return 0 214 | end 215 | 216 | local srow, _, erow = node:range() 217 | 218 | local is_processed = false 219 | 220 | if 221 | not is_processed_by_row[srow] 222 | and ((q['indent.branch'][node:id()] and srow == lnum - 1) or (q['indent.dedent'][node:id()] and srow ~= lnum - 1)) 223 | then 224 | indent = indent - indent_size 225 | is_processed = true 226 | end 227 | 228 | -- do not indent for nodes that starts-and-ends on same line and starts on target line (lnum) 229 | local should_process = not is_processed_by_row[srow] 230 | local is_in_err = false 231 | if should_process then 232 | local parent = node:parent() 233 | is_in_err = parent ~= nil and parent:has_error() 234 | end 235 | if 236 | should_process 237 | and ( 238 | q['indent.begin'][node:id()] 239 | and (srow ~= erow or is_in_err or q['indent.begin'][node:id()]['indent.immediate']) 240 | and (srow ~= lnum - 1 or q['indent.begin'][node:id()]['indent.start_at_same_line']) 241 | ) 242 | then 243 | indent = indent + indent_size 244 | is_processed = true 245 | end 246 | 247 | if is_in_err and not q['indent.align'][node:id()] then 248 | -- only when the node is in error, promote the 249 | -- first child's aligned indent to the error node 250 | -- to work around ((ERROR "X" . (_)) @aligned_indent (#set! "delimiter" "AB")) 251 | -- matching for all X, instead set do 252 | -- (ERROR "X" @aligned_indent (#set! "delimiter" "AB") . (_)) 253 | -- and we will fish it out here. 254 | for c in node:iter_children() do 255 | if q['indent.align'][c:id()] then 256 | q['indent.align'][node:id()] = q['indent.align'][c:id()] 257 | break 258 | end 259 | end 260 | end 261 | -- do not indent for nodes that starts-and-ends on same line and starts on target line (lnum) 262 | if should_process and q['indent.align'][node:id()] and (srow ~= erow or is_in_err) and (srow ~= lnum - 1) then 263 | local metadata = q['indent.align'][node:id()] 264 | local o_delim_node, o_is_last_in_line ---@type TSNode|nil, boolean|nil 265 | local c_delim_node, c_is_last_in_line ---@type TSNode|nil, boolean|nil, boolean|nil 266 | local indent_is_absolute = false 267 | if metadata['indent.open_delimiter'] then 268 | o_delim_node, o_is_last_in_line = find_delimiter(bufnr, node, metadata['indent.open_delimiter']) 269 | else 270 | o_delim_node = node 271 | end 272 | if metadata['indent.close_delimiter'] then 273 | c_delim_node, c_is_last_in_line = find_delimiter(bufnr, node, metadata['indent.close_delimiter']) 274 | else 275 | c_delim_node = node 276 | end 277 | 278 | if o_delim_node then 279 | local o_srow, o_scol = o_delim_node:start() 280 | local c_srow = nil --- @type integer? 281 | if c_delim_node then 282 | c_srow = c_delim_node:start() 283 | end 284 | if o_is_last_in_line then 285 | -- hanging indent (previous line ended with starting delimiter) 286 | -- should be processed like indent 287 | if should_process then 288 | indent = indent + indent_size * 1 289 | if c_is_last_in_line then 290 | -- If current line is outside the range of a node marked with `@aligned_indent` 291 | -- Then its indent level shouldn't be affected by `@aligned_indent` node 292 | if c_srow and c_srow < lnum - 1 then 293 | indent = math.max(indent - indent_size, 0) 294 | end 295 | end 296 | end 297 | else 298 | -- aligned indent 299 | if c_is_last_in_line and c_srow and o_srow ~= c_srow and c_srow < lnum - 1 then 300 | -- If current line is outside the range of a node marked with `@aligned_indent` 301 | -- Then its indent level shouldn't be affected by `@aligned_indent` node 302 | indent = math.max(indent - indent_size, 0) 303 | else 304 | indent = o_scol + (metadata['indent.increment'] or 1) 305 | indent_is_absolute = true 306 | end 307 | end 308 | -- deal with the final line 309 | local avoid_last_matching_next = false 310 | if c_srow and c_srow ~= o_srow and c_srow == lnum - 1 then 311 | -- delims end on current line, and are not open and closed same line. 312 | -- then this last line may need additional indent to avoid clashes 313 | -- with the next. `indent.avoid_last_matching_next` controls this behavior, 314 | -- for example this is needed for function parameters. 315 | avoid_last_matching_next = metadata['indent.avoid_last_matching_next'] or false 316 | end 317 | if avoid_last_matching_next then 318 | -- last line must be indented more in cases where 319 | -- it would be same indent as next line (we determine this as one 320 | -- width more than the open indent to avoid confusing with any 321 | -- hanging indents) 322 | if indent <= vim.fn.indent(o_srow + 1) + indent_size then 323 | indent = indent + indent_size * 1 324 | else 325 | indent = indent 326 | end 327 | end 328 | is_processed = true 329 | if indent_is_absolute then 330 | -- don't allow further indenting by parent nodes, this is an absolute position 331 | return indent 332 | end 333 | end 334 | end 335 | 336 | if srow then 337 | is_processed_by_row[srow] = is_processed_by_row[srow] or is_processed 338 | end 339 | 340 | node = node:parent() 341 | end 342 | 343 | return indent 344 | end 345 | 346 | local M = {} 347 | 348 | function M.indentexpr() 349 | return internal.get_indent(vim.v.lnum) 350 | end 351 | 352 | return M 353 | -------------------------------------------------------------------------------- /nvim/plugin/autocommands.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | local keymap = vim.keymap 3 | local methods = vim.lsp.protocol.Methods 4 | 5 | local tempdirgroup = api.nvim_create_augroup('tempdir', { clear = true }) 6 | -- Do not set undofile for files in /tmp 7 | api.nvim_create_autocmd('BufWritePre', { 8 | pattern = '/tmp/*', 9 | group = tempdirgroup, 10 | callback = function() 11 | vim.cmd.setlocal('noundofile') 12 | end, 13 | }) 14 | 15 | local numbertoggle = api.nvim_create_augroup('numbertoggle', { clear = true }) 16 | -- Toggle between relative/absolute line numbers 17 | api.nvim_create_autocmd({ 'BufEnter', 'FocusGained', 'InsertLeave', 'CmdlineLeave', 'WinEnter' }, { 18 | pattern = '*', 19 | group = numbertoggle, 20 | callback = function() 21 | if vim.o.nu and api.nvim_get_mode().mode ~= 'i' then 22 | vim.opt.relativenumber = true 23 | end 24 | end, 25 | }) 26 | 27 | api.nvim_create_autocmd({ 'BufLeave', 'FocusLost', 'InsertEnter', 'CmdlineEnter', 'WinLeave' }, { 28 | pattern = '*', 29 | group = numbertoggle, 30 | callback = function() 31 | if vim.o.nu then 32 | vim.opt.relativenumber = false 33 | vim.cmd.redraw() 34 | end 35 | end, 36 | }) 37 | 38 | local nospell_group = api.nvim_create_augroup('nospell', { clear = true }) 39 | 40 | api.nvim_create_autocmd('TermOpen', { 41 | group = nospell_group, 42 | callback = function() 43 | vim.wo[0].spell = false 44 | end, 45 | }) 46 | 47 | local highlight_cur_n_group = api.nvim_create_augroup('highlight-current-n', { clear = true }) 48 | api.nvim_create_autocmd('ColorScheme', { 49 | callback = function() 50 | local search = api.nvim_get_hl(0, { name = 'Search' }) 51 | ---@diagnostic disable-next-line: cast-type-mismatch 52 | ---@cast search vim.api.keyset.highlight 53 | api.nvim_set_hl(0, 'CurSearch', { link = 'IncSearch' }) 54 | api.nvim_set_hl(0, 'SearchCurrentN', search) 55 | return api.nvim_set_hl(0, 'Search', { link = 'SearchCurrentN' }) 56 | end, 57 | group = highlight_cur_n_group, 58 | }) 59 | api.nvim_create_autocmd('CmdlineEnter', { 60 | pattern = '/,\\?', 61 | callback = function() 62 | vim.opt.hlsearch = true 63 | vim.opt.incsearch = true 64 | return api.nvim_set_hl(0, 'Search', { link = 'SearchCurrentN' }) 65 | end, 66 | group = highlight_cur_n_group, 67 | }) 68 | api.nvim_create_autocmd('CmdlineLeave', { 69 | pattern = '/,\\?', 70 | callback = function() 71 | api.nvim_set_hl(0, 'Search', {}) 72 | local function _4_() 73 | vim.opt.hlsearch = true 74 | return nil 75 | end 76 | return vim.defer_fn(_4_, 5) ~= nil 77 | end, 78 | group = highlight_cur_n_group, 79 | }) 80 | api.nvim_create_autocmd({ 'InsertEnter', 'CursorMoved' }, { 81 | callback = vim.schedule_wrap(function() 82 | vim.cmd.nohlsearch() 83 | end), 84 | group = highlight_cur_n_group, 85 | }) 86 | local function handle_n_N(key) 87 | do 88 | local function other(mode) 89 | if mode == 'n' then 90 | return 'N' 91 | elseif mode == 'N' then 92 | return 'n' 93 | else 94 | return nil 95 | end 96 | end 97 | local function feed(keys) 98 | return api.nvim_feedkeys(keys, 'n', true) 99 | end 100 | if vim.v.searchforward == 0 then 101 | feed(other(key)) 102 | elseif vim.v.searchforward == 1 then 103 | feed(key) 104 | end 105 | end 106 | return vim.defer_fn(function() 107 | vim.opt.hlsearch = true 108 | return nil 109 | end, 5) 110 | end 111 | keymap.set({ 'n' }, 'n', function() 112 | return handle_n_N('n') 113 | end) 114 | keymap.set({ 'n' }, 'N', function() 115 | return handle_n_N('N') 116 | end) 117 | 118 | ---@param filter 'Function' | 'Module' | 'Struct' 119 | local function filtered_document_symbol(filter) 120 | vim.lsp.buf.document_symbol() 121 | vim.cmd.Cfilter(('[[%s]]'):format(filter)) 122 | end 123 | 124 | local function preview_location_callback(_, result) 125 | if result == nil or vim.tbl_isempty(result) then 126 | return nil 127 | end 128 | local buf, _ = vim.lsp.util.preview_location(result[1], {}) 129 | if buf then 130 | local cur_buf = vim.api.nvim_get_current_buf() 131 | vim.bo[buf].filetype = vim.bo[cur_buf].filetype 132 | end 133 | end 134 | 135 | local function peek_definition() 136 | local params = vim.lsp.util.make_position_params(0, 'utf-8') 137 | return vim.lsp.buf_request(0, methods.textDocument_definition, params, preview_location_callback) 138 | end 139 | 140 | local function peek_type_definition() 141 | local params = vim.lsp.util.make_position_params(0, 'utf-8') 142 | return vim.lsp.buf_request(0, methods.textDocument_typeDefinition, params, preview_location_callback) 143 | end 144 | 145 | local function document_functions() 146 | filtered_document_symbol('Function') 147 | end 148 | 149 | local function document_modules() 150 | filtered_document_symbol('Module') 151 | end 152 | 153 | local function document_structs() 154 | filtered_document_symbol('Struct') 155 | end 156 | 157 | local function code_action() 158 | return require('actions-preview').code_actions() 159 | -- return vim.lsp.buf.code_action() 160 | end 161 | 162 | local function go_to_first_import() 163 | vim.lsp.buf.document_symbol { 164 | on_list = function(lst) 165 | for _, results in pairs(lst) do 166 | if type(results) ~= 'table' then 167 | goto Skip 168 | end 169 | for _, result in ipairs(results) do 170 | if result.kind == 'Module' then 171 | local lnum = result.lnum 172 | vim.api.nvim_input("m'") 173 | vim.api.nvim_win_set_cursor(0, { lnum, 0 }) 174 | return 175 | end 176 | end 177 | end 178 | ::Skip:: 179 | vim.notify('No imports found.', vim.log.levels.WARN) 180 | end, 181 | } 182 | end 183 | 184 | local lsp_augroup = vim.api.nvim_create_augroup('UserLspConfig', {}) 185 | 186 | vim.api.nvim_create_autocmd('LspProgress', { 187 | group = lsp_augroup, 188 | once = true, 189 | callback = function() 190 | require('fidget').setup { 191 | -- Options related to LSP progress subsystem 192 | progress = { 193 | ignore_done_already = true, -- Ignore new tasks that are already complete 194 | 195 | -- Options related to how LSP progress messages are displayed as notifications 196 | display = { 197 | render_limit = 3, -- How many LSP messages to show at once 198 | }, 199 | }, 200 | notification = { 201 | override_vim_notify = false, 202 | }, 203 | } 204 | end, 205 | }) 206 | 207 | vim.api.nvim_create_autocmd('LspAttach', { 208 | group = lsp_augroup, 209 | callback = function(ev) 210 | if not vim.g.initial_lsp_attach_done then 211 | local default_on_codelens = vim.lsp.codelens.on_codelens 212 | ---@diagnostic disable-next-line: duplicate-set-field 213 | vim.lsp.codelens.on_codelens = function(err, lenses, ctx, _) 214 | if err or not lenses or not next(lenses) then 215 | return default_on_codelens(err, lenses, ctx, _) 216 | end 217 | for _, lens in pairs(lenses) do 218 | if lens and lens.command and lens.command.title then 219 | lens.command.title = ' ' .. lens.command.title 220 | end 221 | end 222 | return default_on_codelens(err, lenses, ctx, _) 223 | end 224 | require('actions-preview').setup { 225 | backend = { 'telescope' }, 226 | } 227 | vim.g.initial_lsp_attach_done = true 228 | end 229 | 230 | local bufnr = ev.buf 231 | local client = vim.lsp.get_client_by_id(ev.data.client_id) 232 | if not client then 233 | return 234 | end 235 | vim.cmd.setlocal('signcolumn=yes') 236 | 237 | local function buf_set_var(...) 238 | api.nvim_buf_set_var(bufnr, ...) 239 | end 240 | 241 | vim.bo[bufnr].bufhidden = 'hide' 242 | 243 | buf_set_var('lsp_client_id', client.id) 244 | 245 | local function desc(description) 246 | return { noremap = true, silent = true, buffer = bufnr, desc = description } 247 | end 248 | 249 | -- Mappings. 250 | keymap.set('n', 'gD', vim.lsp.buf.declaration, desc('lsp: go to [D]eclaration')) 251 | keymap.set('n', 'gd', vim.lsp.buf.definition, desc('lsp: go to [d]efinition')) 252 | keymap.set('n', 'gi', vim.lsp.buf.implementation, desc('lsp: go to [i]mplementation')) 253 | keymap.set('n', 'gt', vim.lsp.buf.type_definition, desc('lsp: go to [t]ype definition')) 254 | keymap.set('n', 'pd', peek_definition, desc('lsp: [p]eek [d]efinition')) 255 | keymap.set('n', 'pt', peek_type_definition, desc('lsp: [p]eek [t]ype definition')) 256 | keymap.set('n', 'gi', go_to_first_import, desc('lsp: [g]o to fist [i]mport')) 257 | keymap.set('n', '', vim.lsp.buf.signature_help, desc('lsp: signature help')) 258 | keymap.set('n', 'wa', vim.lsp.buf.add_workspace_folder, desc('lsp: [w]orkspace folder [a]dd')) 259 | keymap.set('n', 'wr', vim.lsp.buf.remove_workspace_folder, desc('lsp: [w]orkspace folder [r]emove')) 260 | keymap.set('n', 'wl', function() 261 | -- TODO: Replace this with a Telescope extension? 262 | vim.print(vim.lsp.buf.list_workspace_folders()) 263 | end, desc('lsp: [w]orkspace folders [l]')) 264 | keymap.set('n', 'rn', function() 265 | -- vim.lsp.buf.rename() 266 | require('live-rename').rename() 267 | end, desc('lsp: [r]e[n]ame')) 268 | keymap.set('n', 'ri', function() 269 | require('live-rename').rename { text = '', insert = true } 270 | end, desc('lsp: [r]ename ([i]nsert mode)')) 271 | keymap.set('n', 'wq', vim.lsp.buf.workspace_symbol, desc('lsp: [w]orkspace symbol [q]uery')) 272 | keymap.set('n', 'dd', vim.lsp.buf.document_symbol, desc('lsp: [dd]ocument symbol')) 273 | keymap.set('n', 'df', document_functions, desc('lsp: [d]ocument [f]unctions')) 274 | keymap.set('n', 'ds', document_structs, desc('lsp: [d]ocument [s]tructs')) 275 | keymap.set('n', 'di', document_modules, desc('lsp: [d]ocument modules/[i]mports')) 276 | if client.name == 'rust-analyzer' then 277 | keymap.set('n', '', function() 278 | vim.cmd.RustLsp('codeAction') 279 | end, desc('rust: code action')) 280 | else 281 | keymap.set('n', '', code_action, desc('lsp: code action')) 282 | end 283 | keymap.set('n', '', vim.lsp.codelens.run, desc('lsp: run code lens')) 284 | keymap.set('n', 'cr', vim.lsp.codelens.refresh, desc('lsp: refresh [c]ode [l]enses')) 285 | 286 | local codelens = require('mrcjk.lsp.codelens') 287 | keymap.set('n', '[l', codelens.goto_prev, desc('lsp: previous code[l]ens')) 288 | keymap.set('n', ']l', codelens.goto_next, desc('lsp: next code[l]ens')) 289 | keymap.set('n', 'gr', vim.lsp.buf.references, desc('lsp: [g]et [r]eferences')) 290 | keymap.set({ 'n', 'v' }, 'f', function() 291 | vim.lsp.buf.format { async = true } 292 | end, desc('lsp: [f]ormat buffer')) 293 | 294 | if client:supports_method(methods.textDocument_inlayHint) then 295 | keymap.set('n', 'h', function() 296 | local current_setting = vim.lsp.inlay_hint.is_enabled { bufnr = bufnr } 297 | vim.lsp.inlay_hint.enable(not current_setting, { bufnr = bufnr }) 298 | end, desc('lsp: toggle inlay [h]ints')) 299 | end 300 | 301 | local group = api.nvim_create_augroup(string.format('lsp-%s-%s', bufnr, client.id), {}) 302 | if client:supports_method(methods.codeLens_resolve) then 303 | vim.api.nvim_create_autocmd({ 'InsertLeave', 'BufEnter', 'CursorHold' }, { 304 | group = group, 305 | callback = function() 306 | vim.lsp.codelens.refresh { bufnr = bufnr } 307 | end, 308 | buffer = bufnr, 309 | }) 310 | vim.lsp.codelens.refresh { bufnr = bufnr } 311 | end 312 | if client:supports_method(methods.textDocument_completion, bufnr) then 313 | vim.lsp.completion.enable(true, client.id, bufnr, { autotrigger = false }) 314 | end 315 | end, 316 | }) 317 | 318 | api.nvim_create_autocmd('LspDetach', { 319 | group = api.nvim_create_augroup('lsp-detach', {}), 320 | callback = function(args) 321 | local group = api.nvim_create_augroup(string.format('lsp-%s-%s', args.buf, args.data.client_id), {}) 322 | pcall(api.nvim_del_augroup_by_name, group) 323 | end, 324 | }) 325 | 326 | api.nvim_create_autocmd({ 'VimEnter', 'FocusGained', 'BufEnter' }, { 327 | group = api.nvim_create_augroup('ReloadFileOnChange', {}), 328 | command = 'checktime', 329 | }) 330 | 331 | local function should_highlight_extra_whitespace() 332 | local ignored_filetypes = { 333 | 'TelescopePrompt', 334 | 'help', 335 | 'dashboard', 336 | } 337 | if vim.bo.buftype == 'nofile' or vim.bo.buftype == 'terminal' then 338 | return false 339 | end 340 | if vim.tbl_contains(ignored_filetypes, vim.bo.filetype) then 341 | return false 342 | end 343 | if api.nvim_get_mode().mode == 'i' then 344 | return false 345 | end 346 | local bufnr = api.nvim_get_current_buf() 347 | require('editorconfig') 348 | local editorconfig = vim.b[bufnr].editorconfig or {} 349 | if editorconfig.trim_trailing_whitespace == 'true' then 350 | return false 351 | end 352 | return true 353 | end 354 | 355 | api.nvim_create_autocmd({ 'UIEnter' }, { 356 | group = api.nvim_create_augroup('HighlightExtraWhiteSpace', {}), 357 | callback = function() 358 | local extra_whitespace_hi = 'DiffDelete' 359 | if not vim.fn.hlexists(extra_whitespace_hi) then 360 | vim.notify_once(string.format('highlight %s does not exist', extra_whitespace_hi), vim.log.levels.ERROR) 361 | return 362 | end 363 | if should_highlight_extra_whitespace() then 364 | vim.cmd.match { extra_whitespace_hi, [[/\s\+$/]] } 365 | else 366 | vim.cmd.match() 367 | end 368 | end, 369 | }) 370 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------