Mercurial > dotfiles
view .chezmoitemplates/init.lua @ 857:bdd538139b08
feat: enable some hgrc options and add templates
author | Zeger Van de Vannet <zeger@vandevan.net> |
---|---|
date | Wed, 27 Mar 2024 08:50:12 +0100 |
parents | 1108123bd416 |
children |
line wrap: on
line source
-- -- Neovim dotfiles -- -- local home = os.getenv 'HOME' if home == nil then home = os.getenv 'USERPROFILE' end -- Bootstrap lazy local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' if not vim.loop.fs_stat(lazypath) then vim.fn.system { 'git', 'clone', '--filter=blob:none', 'https://github.com/folke/lazy.nvim.git', '--branch=stable', -- latest stable release lazypath, } end vim.opt.rtp:prepend(lazypath) -- Set leader to space vim.g.mapleader = ' ' require('lazy').setup({ -- General plugins 'tpope/vim-sensible', { 'tpope/vim-repeat' }, { 'tpope/vim-rsi' }, { 'sgur/vim-editorconfig' }, { 'ojroques/nvim-osc52', config = function() require('osc52').setup { trim = true } local copy = function(lines, _) require('osc52').copy(table.concat(lines, '\n')) end local paste = function() return { vim.fn.split(vim.fn.getreg '', '\n'), vim.fn.getregtype '' } end vim.g.clipboard = { name = 'osc52', copy = { ['+'] = copy, ['*'] = copy }, paste = { ['+'] = paste, ['*'] = paste }, } end, }, { 'tpope/vim-eunuch', cmd = { 'Delete', 'Unlink', 'Move', 'Rename', 'Mkdir', 'Chmod', 'Cfind', 'Clocate', 'Lfind', 'Llocate', 'SudoEdit', 'SudoWrite', 'Wall', }, }, -- Smooth scrolling { 'karb94/neoscroll.nvim', config = function() require('neoscroll').setup {} end, }, -- Library with lua functions { 'nvim-lua/plenary.nvim' }, -- Spelling/autocorrection { 'tpope/vim-abolish' }, -- Git/VCS { 'vim-scripts/gitignore' }, { 'tpope/vim-git', ft = { 'gitcommit', 'gitrebase' } }, { 'zegervdv/diffview.nvim', branch = 'hg-linetrace-2', dev = true, config = function() require('diffview').setup { use_icons = false, icons = { folder_closed = '+', folder_open = '-', }, signs = { fold_closed = '+', fold_open = '-', done = '✓', }, hg_cmd = { 'rhg' }, view = { merge_tool = { layout = 'diff4_mixed', }, }, default_args = { DiffviewOpen = { '--untracked-files=no' }, }, } local wk = require 'which-key' wk.register { ['<leader>d'] = { name = 'Diffview' } } vim.keymap.set('n', '<leader>do', '<cmd>DiffviewOpen<CR>', { desc = 'Open Diffview' }) vim.keymap.set('n', '<leader>df', '<cmd>DiffviewFileHistory %<CR>', { desc = 'Show history for current file' }) vim.keymap.set('n', '<leader>dh', ':DiffviewFileHistory ', { desc = 'Show history' }) vim.keymap.set('n', '<leader>dc', '<cmd>DiffviewClose<CR>', { desc = 'Close Diffview window' }) end, }, -- Comments { 'numToStr/Comment.nvim', config = function() local ft = require 'Comment.ft' ft.systemverilog = { '//%s', '/*%s*/' } ft.verilog = { '//%s', '/*%s*/' } require('Comment').setup { padding = true, sticky = true, ignore = '^(%s*)$', mappings = { basic = true, extra = true, }, } end, keys = { { 'gc', mode = 'n', desc = 'Comment toggle' }, { 'gb', mode = 'n', desc = 'Comment Block toggle' }, { 'gc', mode = 'v', desc = 'Comment toggle' }, { 'gb', mode = 'v', desc = 'Comment block toggle' }, }, }, -- Parentheses etc { 'kylechui/nvim-surround', config = function() require('nvim-surround').setup() end, }, { 'windwp/nvim-autopairs', config = function() local npairs = require 'nvim-autopairs' local Rule = require 'nvim-autopairs.rule' local cmp = require 'nvim-autopairs.completion.cmp' require('cmp').event:on('confirm_done', cmp.on_confirm_done()) npairs.setup { ignored_next_char = string.gsub([[ [%w%%%'%[%.] ]], '%s+', ''), enable_afterquote = false, } npairs.add_rules { Rule(' ', ' '):with_pair(function(opts) local pair = opts.line:sub(opts.col - 1, opts.col) return vim.tbl_contains({ '()', '[]', '{}' }, pair) end), Rule('( ', ' )') :with_pair(function() return false end) :with_move(function(opts) return opts.prev_char:match '.%)' ~= nil end) :use_key ')', Rule('{ ', ' }') :with_pair(function() return false end) :with_move(function(opts) return opts.prev_char:match '.%}' ~= nil end) :use_key '}', Rule('[ ', ' ]') :with_pair(function() return false end) :with_move(function(opts) return opts.prev_char:match '.%]' ~= nil end) :use_key ']', } npairs.get_rule('`'):with_pair(function() return vim.bo.filetype ~= 'systemverilog' end) npairs.get_rule("'")[1]:with_pair(function() return vim.bo.filetype ~= 'systemverilog' end) end, }, -- Moving around within lines { 'wellle/targets.vim', event = 'InsertEnter *' }, -- Search -- Opening files { 'wsdjeg/vim-fetch' }, -- session management { 'folke/persistence.nvim', event = 'BufReadPre', config = function() require('persistence').setup() end, }, -- Indent lines { 'lukas-reineke/indent-blankline.nvim', config = function() vim.g.indent_blankline_buftype_exclude = { 'terminal', 'help', 'nofile' } vim.g.indent_blankline_show_first_indent_level = false vim.g.indent_blankline_char = '│' end, }, -- Increment/decrement { 'zegervdv/nrpattern.nvim', branch = 'lua', dependencies = 'tpope/vim-repeat', config = function() local nrpattern = require 'nrpattern' local defaults = require 'nrpattern.default' defaults[{ 'input', 'output' }] = { priority = 12, filetypes = { 'verilog', 'systemverilog' } } defaults[{ "'1", "'0" }] = { priority = 9, filetypes = { 'verilog', 'systemverilog' } } nrpattern.setup(defaults) end, }, -- Tmux { 'numtostr/navigator.nvim', config = function() require('Navigator').setup { auto_save = 'current', disable_on_zoom = true } local nmap = function(lhs, rhs, opts) return vim.keymap.set('n', lhs, rhs, opts) end nmap('<c-h>', require('Navigator').left, { silent = true }) nmap('<c-j>', require('Navigator').down, { silent = true }) nmap('<c-k>', require('Navigator').up, { silent = true }) nmap('<c-l>', require('Navigator').right, { silent = true }) end, }, -- Keymap help { 'folke/which-key.nvim', config = function() require('which-key').setup { plugins = { spelling = { enabled = true, suggestions = 20, }, }, triggers = { '<leader>', 'g', '<c-w>', '"', '`', 'z' }, } end, }, -- Completion/snippets/LSP { 'neovim/nvim-lspconfig' }, { 'hrsh7th/nvim-cmp', dependencies = { 'hrsh7th/cmp-buffer', 'hrsh7th/cmp-nvim-lsp', 'saadparwaiz1/cmp_luasnip', 'hrsh7th/cmp-path', 'hrsh7th/cmp-cmdline', 'hrsh7th/cmp-nvim-lsp-signature-help', }, config = function() local cmp = require 'cmp' local luasnip = require 'luasnip' cmp.setup { snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, mapping = { ['<C-p>'] = { i = cmp.mapping.select_prev_item() }, ['<C-n>'] = { i = cmp.mapping.select_next_item() }, ['<C-d>'] = { i = cmp.mapping.scroll_docs(-4) }, ['<C-y>'] = { i = cmp.mapping.complete() }, ['<C-e>'] = { i = cmp.mapping.close() }, ['<CR>'] = { i = cmp.mapping.confirm { behavior = cmp.ConfirmBehavior.Replace } }, ['<C-k>'] = { i = cmp.mapping.confirm { behavior = cmp.ConfirmBehavior.Replace } }, }, sources = { { name = 'nvim_lsp' }, { name = 'nvim_lsp_signature_help' }, { name = 'buffer', keyword_length = 5 }, { name = 'luasnip' }, { name = 'path' }, }, experimental = { native_menu = false, ghost_text = true, }, } cmp.setup.cmdline(':', { mapping = cmp.mapping.preset.cmdline { ['<C-p>'] = { c = cmp.mapping.select_prev_item() }, ['<C-n>'] = { c = cmp.mapping.select_next_item() }, ['<C-y>'] = { c = cmp.mapping.complete() }, }, sources = cmp.config.sources({ { name = 'path' }, }, { { name = 'cmdline', keyword_length = 4 }, }), }) end, }, { 'nvim-treesitter/nvim-treesitter', version = 'v0.9.1', config = function() require 'nvim-treesitter.highlight' require('nvim-treesitter.configs').setup { ensure_installed = { 'python', 'lua', 'verilog', 'json', 'yaml', 'bash', 'dockerfile', 'c', 'cpp', 'regex', 'markdown', 'rst', 'beancount', 'rust', }, indent = { enable = false, }, highlight = { enable = true, disable = { 'systemverilog', 'verilog' }, }, incremental_selection = { enable = true, keymaps = { init_selection = 'gnn', node_incremental = 'grn', scope_incremental = 'grc', node_decremental = 'grm', }, }, refactor = { highlight_definitions = { enable = true }, smart_rename = { enable = true, keymaps = { smart_rename = 'gsr' } }, navigation = { enable = true, keymaps = { goto_definition = 'gnd', list_definitions = 'gnD' }, }, }, textobjects = { move = { enable = true, goto_next_start = { [']]'] = '@block.outer' }, goto_previous_start = { ['[['] = '@block.outer' }, goto_next_end = { [']['] = '@block.outer' }, goto_previous_end = { ['[]'] = '@block.outer' }, }, swap = { enable = true, swap_next = { ['<A-n>'] = '@parameter.inner', }, swap_previous = { ['<A-p>'] = '@parameter.inner', }, }, select = { enable = true, lookahead = true, keymaps = { ['af'] = '@function.outer', ['if'] = '@function.inner', ['ab'] = '@block.outer', ['ib'] = '@block.inner', }, }, }, playground = { enable = true, disable = {}, updatetime = 25, persist_queries = false }, } end, }, 'nvim-treesitter/nvim-treesitter-refactor', 'nvim-treesitter/nvim-treesitter-textobjects', { 'nvim-treesitter/playground', cmd = 'TSPlaygroundToggle' }, { 'L3MON4D3/luasnip', config = function() require('luasnip').setup { enable_autosnippets = true } end }, { 'rmagatti/goto-preview', config = function() require('goto-preview').setup {} end, }, { 'jose-elias-alvarez/null-ls.nvim', dependencies = 'nvim-lua/plenary.nvim', }, { 'folke/neodev.nvim', version = '2.5.x' }, { 'smjonas/inc-rename.nvim', config = function() require('inc_rename').setup { post_hook = function(result) local changed = {} for uri, changes in pairs(result.changes or result.documentChanges) do local bufnr = vim.uri_to_bufnr(uri) for _, edits in ipairs(changes) do table.insert(changed, { bufnr = bufnr, lnum = edits.range.start.line + 1, col = edits.range.start.character + 1, text = vim.api.nvim_buf_get_lines(bufnr, edits.range.start.line, edits.range.start.line + 1, false)[1], }) end end vim.fn.setqflist(changed, 'r') end, } end, }, { 'lewis6991/tree-sitter-tcl', build = 'make' }, { 'yaocccc/nvim-hl-mdcodeblock.lua', after = 'nvim-treesitter', config = function() require('hl-mdcodeblock').setup {} end, }, { 'joechrisellis/lsp-format-modifications.nvim', dependencies = { 'nvim-lua/plenary.nvim' }, }, { 'vigoux/notifier.nvim', config = function() require('notifier').setup { status_width = 70 } end, }, { 'SmiteshP/nvim-navic', dependencies = { 'neovim/nvim-lspconfig' }, }, { 'ThePrimeagen/refactoring.nvim', dependencies = { 'nvim-lua/plenary.nvim', 'nvim-treesitter/nvim-treesitter', }, config = function() local refactoring = require 'refactoring' local wk = require 'which-key' refactoring.setup {} wk.register { ['<leader>r'] = { name = 'Refactoring' } } local maps = { { mode = 'v', key = 'e', name = 'Extract Function' }, { mode = 'v', key = 'f', name = 'Extract Function To File' }, { mode = 'v', key = 'v', name = 'Extract Variable' }, { mode = 'v', key = 'i', name = 'Inline Variable' }, { mode = 'n', key = 'b', name = 'Extract Block' }, { mode = 'n', key = 'bf', name = 'Extract Block To File' }, { mode = 'n', key = 'i', name = 'Inline Variable' }, } for _, map in ipairs(maps) do vim.keymap.set( map.mode, '<leader>r' .. map.key, function() refactoring.refactor(map.name) end, { desc = map.name, silent = true, expr = false } ) end vim.keymap.set( 'n', '<leader>rpp', function() refactoring.debug.printf { below = false } end, { desc = 'Add debug print statement', silent = true } ) vim.keymap.set( 'n', '<leader>rpv', function() refactoring.debug.print_var { normal = true } end, { desc = 'Print variable', silent = true } ) vim.keymap.set( 'v', '<leader>rpv', function() refactoring.debug.print_var() end, { desc = 'Print variable', silent = true } ) vim.keymap.set( 'n', '<leader>rpc', function() refactoring.debug.cleanup {} end, { desc = 'Clean up debug prints', silent = true } ) end, }, { 'ibhagwan/fzf-lua', config = function() local fzf = require 'fzf-lua' fzf.setup { winopts = { border = 'single', }, } fzf.register_ui_select() local grep_opts = function(pattern) local utils = require 'fzf-lua.utils' local config = require 'fzf-lua.config' local args = utils.input 'rg opts> ' local rg_opts = config.globals.grep.rg_opts .. ' ' .. args local opts = config.normalize_opts({ rg_opts = rg_opts }, config.globals.grep) opts.search = pattern fzf.live_grep(opts) end local get_selected_word = function() local start_pos = vim.fn.getpos "'[" local end_pos = vim.fn.getpos "']" local start_line = math.min(start_pos[2], end_pos[2]) - 1 local end_line = math.max(start_pos[2], end_pos[2]) local start_col = math.min(start_pos[3], end_pos[3]) local end_col = math.max(start_pos[3], end_pos[3]) local line = vim.api.nvim_buf_get_lines(0, start_line, end_line, true) return line[1]:sub(start_col, end_col) end vim.keymap.set({ 'n' }, '<leader>fg', function() grep_opts() end, { desc = 'Live grep prompting for options' }) vim.keymap.set({ 'n' }, '<leader>ff', function() fzf.files() end, { desc = 'Search for files' }) function _G.__live_grep(motion) local word = get_selected_word() fzf.live_grep { search = word } end vim.keymap.set({ 'x', 'n' }, 'gs', function() vim.o.operatorfunc = 'v:lua.__live_grep' return 'g@' end, { expr = true, desc = 'Live grep for word' }) function _G.__live_grep_opts(motion) local word = get_selected_word() grep_opts(word) end vim.keymap.set({ 'x', 'n' }, 'gt', function() vim.o.operatorfunc = 'v:lua.__live_grep_opts' return 'g@' end, { expr = true, desc = 'Live grep for word with options' }) end, }, { 'vimjas/vim-python-pep8-indent', ft = { 'python' } }, { 'rebelot/heirline.nvim', config = function() local utils = require 'heirline.utils' local conditions = require 'heirline.conditions' local colors if os.getenv 'DARKMODE' then colors = require('catppuccin.palettes').get_palette 'macchiato' colors.diag_warn = utils.get_highlight('DiagnosticSignWarn').fg colors.diag_error = utils.get_highlight('DiagnosticSignError').fg else colors = { blue = '#2f6f9f', green = '#73b00a', orange = '#e9a700', diag_warn = '#e9a700', diag_error = '#f93232', } end require('heirline').load_colors(colors) local align = { provider = '%=' } local space = { provider = ' ' } local lbound = { provider = '▊ ', hl = { fg = 'blue', bg = 'bg' } } local rbound = { provider = ' ▊', hl = { fg = 'blue', bg = 'bg' } } local FileNameBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, } local FileName = { provider = function(self) local filename = vim.fn.fnamemodify(self.filename, ':.') if filename == '' then return '[No Name]' end if not conditions.width_percent_below(#filename, 0.25) then filename = vim.fn.pathshorten(filename) end return filename end, hl = { fg = 'blue' }, } local FileFlags = { { provider = function() if vim.bo.modified then return ' [+]' end end, hl = { fg = 'green' }, }, { provider = function() if not vim.bo.modifiable or vim.bo.readonly then return ' RO' end end, hl = { fg = 'orange' }, }, } FileNameBlock = utils.insert(FileNameBlock, FileName, unpack(FileFlags), { provider = '%<' }) local Ruler = { provider = '%l : %c %P' } local Lsp = { condition = conditions.lsp_attached, update = { 'LspAttach', 'LspDetach' }, provider = function() local names = {} for _, server in pairs(vim.lsp.get_active_clients { bufnr = 0 }) do table.insert(names, server.name) end return table.concat(names, ', ') end, hl = { fg = 'green' }, } local Diagnostics = { condition = conditions.has_diagnostics, init = function(self) self.errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR }) self.warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) end, update = { 'DiagnosticChanged', 'BufEnter' }, { provider = function(self) return self.errors > 0 and self.errors .. ' ' end, hl = { fg = 'diag_error' }, }, { provider = function(self) return self.warnings > 0 and self.warnings .. ' ' end, }, hl = { fg = 'diag_warn' }, on_click = { callback = function() local diagnostics = vim.diagnostic.get(0, { severity = { min = vim.diagnostic.severity.WARN } }) vim.fn.setqflist(vim.diagnostic.toqflist(diagnostics)) vim.cmd.copen { mods = { split = 'botright' } } end, name = 'heirline_diagnostics', }, } local Window = { provider = function() return '- ' .. vim.api.nvim_win_get_number(0) .. ' -' end, hl = { fg = 'blue' }, } local statusline_default = { lbound, FileNameBlock, align, Diagnostics, Lsp, space, Ruler, rbound } local statusline_inactive = { condition = function() return not conditions.is_active() end, lbound, FileNameBlock, align, Window, rbound, } local statusline = { fallthrough = false, hl = { bg = 'bg' }, statusline_inactive, statusline_default, } local navic = require 'nvim-navic' local winbar = { fallthrough = false, condition = function() return navic.is_available() end, static = { -- bit operation dark magic, see below... enc = function(line, col, winnr) return bit.bor(bit.lshift(line, 16), bit.lshift(col, 6), winnr) end, -- line: 16 bit (65535); col: 10 bit (1023); winnr: 6 bit (63) dec = function(c) local line = bit.rshift(c, 16) local col = bit.band(bit.rshift(c, 6), 1023) local winnr = bit.band(c, 63) return line, col, winnr end, }, init = function(self) local data = navic.get_data() or {} local children = {} for i, loc in ipairs(data) do local pos = self.enc(loc.scope.start.line, loc.scope.start.character, self.winnr) local child = { { provider = loc.name, on_click = { minwid = pos, callback = function(_, minwid) local line, col, winnr = self.dec(minwid) vim.api.nvim_win_set_cursor(vim.fn.win_getid(winnr), { line, col }) end, name = 'heirline_navic', }, }, } if #data > 1 and i < #data then table.insert(child, { provider = ' > ' }) end table.insert(children, child) end self.child = self:new(children, 1) end, provider = function(self) return self.child:eval() end, update = 'CursorMoved', } require('heirline').setup { statusline = statusline, winbar = winbar, opts = { disable_winbar_cb = function(args) return not conditions.buffer_matches { filetype = { 'python', 'verilog', 'systemverilog', 'cpp', 'c' } } -- TODO: If LSP is not attached when opening the window, this does not work -- return not navic.is_available(args.bufnr) end, }, } end, }, -- File navigation { 'elihunter173/dirbuf.nvim', config = function() require('dirbuf').setup { hash_padding = 2, show_hidden = true, } end, }, -- Colorscheme { 'zegervdv/espresso-tutti-colori.nvim', cond = not os.getenv 'DARKMODE', dev = true, config = function() vim.cmd.colorscheme 'espresso_tutti_colori' end, priority = 1000, lazy = false, }, { 'catppuccin/nvim', name = 'catppuccin', cond = not not os.getenv 'DARKMODE', config = function() require('catppuccin').setup { flavour = 'macchiato', } vim.cmd.colorscheme 'catppuccin-macchiato' end, priority = 1000, lazy = false, }, -- Notekeeping { 'mickael-menu/zk-nvim', }, -- Integration with external tools { 'glacambre/firenvim', build = function() vim.fn['firenvim#install'](0) end, config = function() vim.g.firenvim_config = { localSettings = { ['.*'] = { takeover = 'never', }, }, } end, }, }, { dev = { path = '~/Projects', fallback = true, }, ui = { icons = { cmd = '⌘', config = '🛠', event = '📅', ft = '📂', init = '⚙', keys = '🗝', plugin = '🔌', runtime = '💻', source = '📄', start = '🚀', task = '📌', lazy = '💤 ', }, }, }) -- Configuration vim.opt.backspace = { 'indent', 'eol', 'start' } -- Backspace everything vim.opt.autoread = true -- Read changed files vim.opt.hidden = true -- Allow to move away from modified files vim.opt.autowriteall = true -- Write changes when losing focus -- Visuals vim.opt.number = true vim.opt.relativenumber = true vim.opt.scrolloff = 4 vim.opt.showcmd = true -- Show incomplete commands while typing vim.opt.termguicolors = true vim.opt.background = 'dark' vim.opt.showmatch = true -- Highligh matching braces vim.opt.wrap = true -- Wrap lines vim.opt.wrapmargin = 2 -- Stay 2 chars from side vim.opt.textwidth = 79 vim.opt.colorcolumn = '81' -- Show indication of 81 chars vim.opt.linebreak = true -- Smarter wrapping vim.opt.breakindent = true -- Indent wrapped lines to same level vim.opt.fixendofline = true -- Add EOL when missing vim.opt.expandtab = true -- Add spaces when pressing tab vim.opt.tabstop = 2 -- Tab is 2 spaces vim.opt.shiftwidth = 2 -- Shift per 2 spaces vim.opt.shiftround = true -- Round shifts to allign (1 space + tab = 2 spaces) -- Searching and substitute vim.opt.magic = true -- Enable regexes vim.opt.hlsearch = true -- Highlight all matches vim.opt.incsearch = true -- Show matches while typing vim.opt.ignorecase = true vim.opt.smartcase = true -- When search pattern contains cases, be case sensitive vim.opt.gdefault = true -- Use global flag for substitute: replace all matches on line vim.opt.inccommand = 'nosplit' -- Show live replacements directly in text vim.opt.autoindent = true vim.opt.cindent = true -- C-syntax based indenting vim.opt.updatetime = 300 -- Faster triggering of CursorHold events vim.opt.errorbells = false -- Don't you beep to me vim.opt.title = false -- Don't set the shell title vim.opt.history = 1000 -- Remember last commands vim.opt.wildmenu = true -- Command completion vim.opt.wildmode = 'longest:full,full' vim.opt.shortmess:append 'c' -- Hide ins-completion messages vim.opt.ttyfast = true -- fast terminal vim.opt.lazyredraw = true vim.opt.ttimeoutlen = -1 -- Minimum timeout vim.opt.diffopt:append 'iwhite' -- Ignore whitespace in diffs vim.opt.diffopt:append 'internal' -- Internal diff engine vim.opt.diffopt:append 'algorithm:patience' -- Use patience algorithm vim.opt.diffopt:append 'linematch:60' -- Second order diff to align lines vim.opt.tags = { '.git/tags', 'tags' } vim.opt.path:append '**' -- Recursively search current directory vim.opt.formatoptions = { c = true, -- Wrap comments r = true, -- Continue comments o = true, -- Insert comment with o/O q = true, -- Format comments with gq n = true, -- Indent numbered lists [2] = true, -- Indent from 2nd line of paragraph [1] = true, -- Don't break before one letter words } vim.opt.signcolumn = 'yes' -- Always show signcolumn vim.opt.cursorline = true vim.opt.startofline = false -- When moving try to keep cursor in column -- Show certain characters vim.opt.list = true vim.opt.listchars = { trail = '·', extends = '>', precedes = '<', nbsp = '+', tab = '▸ ' } vim.opt.sessionoptions:remove 'options' -- Remove options from saved sessions (reload from config) vim.opt.completeopt = { 'menu', 'menuone', 'noselect' } vim.opt.splitright = true -- Open new splits to right vim.opt.virtualedit = 'block' -- Enable block editting vim.opt.conceallevel = 0 -- Disable conceal vim.opt.pastetoggle = '<F2>' -- Enable paste mode local optdir = function(path) local Path = require 'plenary.path' local full_path = Path:new(vim.fn.stdpath 'cache' .. '/' .. path) if not full_path:exists() then full_path:mkdir { parents = true } end return full_path .. '//' end vim.opt.undofile = true -- Persistently remember undos vim.opt.undolevels = 1000 vim.opt.undodir = optdir 'undo' vim.opt.swapfile = false -- Disable swap files vim.opt.backup = true -- Keep backups vim.opt.backupdir = optdir 'backup' -- Files to ignore from completion vim.opt.wildignore:append { '*/tmp/*', '*.so', '*.swp', '*.zip', '*.o', '*.bin', '*.elf', '*.hex', '*.eps', '.git/*', '*.dup', '.hg/**', '*.orig', '*.*~', } vim.opt.mouse = { n = true, -- Normal mode i = true, -- Insert mode c = true, -- Commandline mode v = true, -- Visual mode } vim.opt.fillchars:append { diff = '╱', } vim.opt.jumpoptions:append { 'view' } vim.opt.foldmethod = 'expr' vim.opt.foldexpr = 'nvim_treesitter#foldexpr()' vim.opt.foldnestmax = 3 vim.opt.foldminlines = 1 vim.opt.foldtext = [[substitute(getline(v:foldstart),'\\t',repeat('\ ',&tabstop),'g').'...'.trim(getline(v:foldend)) . ' (' . (v:foldend - v:foldstart + 1) . ' lines)']] vim.opt.foldenable = false function _G.qftf(info) local items local ret = {} if info.quickfix == 1 then items = vim.fn.getqflist({ id = info.id, items = 0 }).items else items = vim.fn.getloclist(info.winid, { id = info.id, items = 0 }).items end local limit = 31 local fnameFmt1, fnameFmt2 = '%-' .. limit .. 's', '…%.' .. (limit - 1) .. 's' local validFmt = '%s │%5d:%-3d│%s %s' for i = info.start_idx, info.end_idx do local e = items[i] local fname = '' local str if e.valid == 1 then if e.bufnr > 0 then fname = vim.fn.bufname(e.bufnr) if fname == '' then fname = '[No Name]' else fname = fname:gsub('^' .. vim.env.HOME, '~') end -- char in fname may occur more than 1 width, ignore this issue in order to keep performance if #fname <= limit then fname = fnameFmt1:format(fname) else fname = fnameFmt2:format(fname:sub(1 - limit)) end end local lnum = e.lnum > 99999 and -1 or e.lnum local col = e.col > 999 and -1 or e.col local qtype = e.type == '' and '' or ' ' .. e.type:sub(1, 1):upper() str = validFmt:format(fname, lnum, col, qtype, e.text) else str = e.text end table.insert(ret, str) end return ret end vim.o.qftf = '{info -> v:lua._G.qftf(info)}' -- Clean up terminal codes from strings local t = function(str) return vim.api.nvim_replace_termcodes(str, true, true, true) end -- General keymaps local map = vim.keymap.set -- Move while in insert mode map('i', '<C-f>', '<right>') -- Keep search matches centered map('n', 'n', 'nzzzv') map('n', 'N', 'Nzzzv') -- Very magic search patterns map({ 'n', 'v' }, '/', '/\\v') -- Move through long lines as breaks map('n', 'j', '(v:count ? "j" : "gj")', { expr = true }) map('n', 'k', '(v:count ? "k" : "gk")', { expr = true }) -- Command line search for commands map('c', '<c-n>', '<down>') map('c', '<c-p>', '<up>') -- Clear highlighs map('n', '<leader>l', '<cmd>noh<CR>', { silent = true }) -- Reselect last selection map('n', 'gV', '`[v`]') -- Use backspace as normal in visual mode map('v', '<BS>', 'x', { remap = true }) -- Keep selection when indenting local keep_visual_selection = function(cmd) return function() vim.opt.smartindent = false if vim.fn.mode() == 'V' then return cmd .. t ':set smartindent<CR>gv' else return cmd .. t ':set smartindent<CR>' end end end map('v', '>', keep_visual_selection '>', { expr = true, silent = true, desc = 'Indent right while keeping selection' }) map('v', '<', keep_visual_selection '<', { expr = true, silent = true, desc = 'Indent left while keeping selection' }) -- Do not move on * map('n', '*', function() local view = vim.fn.winsaveview() vim.cmd.normal { '*', bang = true } vim.fn.winrestview(view) end, { silent = true, desc = 'Search word under cursor without moving to first results', }) map('x', 'p', [[ p:if v:register == '"'<Bar>let @@=@0<Bar>endif<CR> ]], { silent = true }) map('n', '<UP>', ':cprev<CR>', { desc = 'Go to previous error/match' }) map('n', '<DOWN>', ':cnext<CR>', { desc = 'Go to next error/match' }) map('n', '<LEFT>', ':cpf<CR>', { desc = 'Go to previous error/match in previous file' }) map('n', '<RIGHT>', ':cnf<CR>', { desc = 'Go to next error/match in next file' }) map('t', '<C-h>', '<C-\\><C-n><C-w>h') map('t', '<C-j>', '<C-\\><C-n><C-w>j') map('t', '<C-k>', '<C-\\><C-n><C-w>k') map('t', '<C-l>', '<C-\\><C-n><C-w>l') map('c', '<CR>', function() local cmdline = vim.fn.getcmdline() if cmdline == 'ls' or cmdline == 'buffers' or cmdline == 'files' then return '<CR>:b' end return '<CR>' end, { expr = true }) -- Clean up screen map('n', '<ESC>', function() vim.cmd.nohlsearch() vim.cmd.cclose() vim.cmd.lclose() end, { desc = 'Clean up screen' }) -- Special highlighting vim.cmd.match { 'ErrorMsg', [[ '^\(<\|=\|>\)\{7\}\([^=].\+\)\?$' ]] } local au = require 'au' -- Highlight yanked text au.TextYankPost = function() vim.highlight.on_yank { timeout = 120 } end -- Automatic cursorline au.group('cline', { { 'WinEnter', '*', function() if vim.bo.buftype ~= 'nofile' then vim.opt_local.cursorline = true end end, }, { 'WinLeave', '*', function() vim.opt_local.cursorline = false end, }, }) -- Save files on focus lost au.FocusLost = function() if not vim.o.readonly and vim.api.nvim_buf_get_name(0) ~= '' then vim.cmd.wa() end end -- Equalize splits after resizing au.VimResized = [[ exe "normal! \<c-w>=" ]] -- Reload diffs after editing au.BufWritePost = function() if vim.o.diff then vim.cmd.diffupdate() end end -- Open file at last position au.BufReadPost = function() if vim.bo.filetype ~= 'gitcommit' and vim.fn.line '\'"' > 0 and vim.fn.line '\'"' <= vim.fn.line '$' then vim.cmd.normal { 'g`"', bang = true } end end -- Remove netrw buffers au.FileType = { 'netrw', 'setlocal bufhidden=delete' } -- Enable spelling au.FileType = { { 'python', 'systemverilog', 'verilog', 'txt', 'lua' }, function() vim.opt_local.spell = true end } -- Terminal au.group('enter_term', { { 'TermOpen', '*', 'startinsert!' }, { 'BufEnter', '*', function() if vim.bo.buftype == 'terminal' then vim.cmd.startinsert { bang = true } end end, }, { 'BufLeave', '*', function() if vim.bo.buftype == 'terminal' then vim.cmd.stopinsert { bang = true } end end, }, }) -- Color number categories in reports and logs au({ 'BufNewFile', 'BufRead', 'BufEnter' }, { '*.rpt,*.log', function() vim.cmd.syntax { 'match', 'String', [["\v<\d{1,3}>"]] } vim.cmd.syntax { 'match', 'Statement', [["\v<\d{4,6}>"]] } vim.cmd.syntax { 'match', 'Function', [["\v<\d{7,9}>"]] } vim.cmd.syntax { 'match', 'Number', [["\v<0+>"]] } vim.cmd.syntax { 'match', 'Error', [["\v\c^error:"]] } vim.wo.wrap = false end, }) -- Apply changes in chezmoi managed files au.group('chezmoi', { { 'BufWritePost', os.getenv 'HOME' .. '/.local/share/chezmoi/*', 'silent !chezmoi apply --source-path %' }, { 'BufWritePost', os.getenv 'HOME' .. '/.local/share/chezmoi/.chezmoitemplates/init.lua', 'silent !chezmoi apply --source-path $HOME/.local/share/chezmoi/dot_config/nvim/init.lua.tmpl', }, }) -- Snippets local ls = require 'luasnip' -- Expand snippet or jump to next placeholder vim.keymap.set({ 'i', 's' }, '<c-k>', function() if ls.expand_or_locally_jumpable() then ls.expand_or_jump() end end, { silent = true, }) -- Go back to previous placeholder vim.keymap.set({ 'i', 's' }, '<c-j>', function() if ls.jumpable(-1) then ls.jump(-1) end end, { silent = true, }) -- Toggle options in snippets vim.keymap.set('i', '<c-l>', function() if ls.choice_active() then ls.change_choice() end end) -- Copy the current file and line number -- inspired by https://github.com/diegoulloao/nvim-file-location vim.keymap.set('n', '<leader>cp', function() local Path = require 'plenary.path' local current_file = Path:new(vim.fn.expand '%') local root = vim.fs.dirname(vim.fs.find({ '.hg', '.git' }, { path = tostring(current_file:parent()), upward = true })[1]) local current_line = vim.fn.line '.' require('osc52').copy(current_file:make_relative(root) .. ':' .. current_line) vim.notify 'Copied file path and line number' end, { desc = 'Yank current path and line number' }) -- Navigate between open windows for i = 1, 6 do vim.keymap.set('n', '<leader>' .. i, i .. '<c-w>w', { desc = 'Go to window ' .. i }) end -- LSP config local lsp = require 'lspconfig' local null_ls = require 'null-ls' local lsp_formatting = function(bufnr) vim.lsp.buf.format { filter = function(client) local force_null_ls = { 'lua', 'beancount' } if vim.tbl_contains(force_null_ls, vim.bo.filetype) then return client.name == 'null-ls' end return true end, bufnr = bufnr, } end local on_attach = function(client, bufnr) local nmap = function(lhs, rhs, opts) return vim.keymap.set('n', lhs, rhs, opts) end local opts = { silent = true, buffer = bufnr } nmap('gp', require('goto-preview').goto_preview_definition, opts) nmap('gP', require('goto-preview').close_all_win, opts) nmap('gd', vim.lsp.buf.declaration, opts) nmap('K', vim.lsp.buf.hover, opts) nmap('gD', vim.lsp.buf.implementation, opts) nmap('1gD', vim.lsp.buf.type_definition, opts) nmap('gr', vim.lsp.buf.references, opts) nmap('g0', vim.lsp.buf.document_symbol, opts) nmap('ga', vim.lsp.buf.code_action, opts) vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(vim.lsp.handlers.hover, { border = 'single' }) vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(vim.lsp.handlers.hover, { border = 'single' }) if client.name == 'lua_ls' then client.server_capabilities.documentFormattingProvider = false client.server_capabilities.documentRangeFormattingProvider = false end if client.server_capabilities.documentSymbolProvider then local navic = require 'nvim-navic' navic.attach(client, bufnr) end if client.supports_method 'textDocument/rangeFormatting' then local root = vim.fs.find({ '.git', '.hg' }, { path = client.config.root_dir }) local vcs = 'git' if root and #root > 0 then vcs = vim.fs.basename(root[1]):sub(2) end vim.notify('Enabled modification formatting via ' .. vcs .. ' using ' .. client.name, vim.log.levels.INFO) local lsp_format_modifications = require 'lsp-format-modifications' lsp_format_modifications.attach(client, bufnr, { format_on_save = false, vcs = vcs }) nmap('<c-p>', function() lsp_format_modifications.format_modifications_current_buffer() end, opts) elseif client.supports_method 'textDocument/formatting' then nmap('<c-p>', function() lsp_formatting(bufnr) end, opts) end end local capabilities = require('cmp_nvim_lsp').default_capabilities() capabilities.textDocument.completion.completionItem.snippetSupport = true capabilities.textDocument.completion.completionItem.resolveSupport = { properties = { 'documentation', 'detail', 'additionalTextEdits', }, } local root_dir = require('lspconfig.util').root_pattern('.git', '.hg') lsp.pyright.setup { on_attach = on_attach, capabilities = capabilities, root_dir = root_dir } lsp.bashls.setup { on_attach = on_attach, capabilities = capabilities } lsp.rust_analyzer.setup { on_attach = on_attach, capabilities = capabilities, root_dir = root_dir } lsp.esbonio.setup { on_attach = on_attach, capabilities = capabilities, root_dir = root_dir, -- Assume esbonio is installed with --user cmd = { home .. '/.local/bin/esbonio' }, } lsp.clangd.setup { on_attach = on_attach, capabilities = capabilities, root_dir = root_dir, } local bean_file = os.getenv 'BEAN_FILE' if bean_file then lsp.beancount.setup { on_attach = on_attach, capabilities = capabilities, root_dir = root_dir, init_options = { journal_file = bean_file, }, } local helpers = require 'null-ls.helpers' local bean_format = { name = 'bean-format', filetypes = { 'beancount' }, method = null_ls.methods.FORMATTING, generator = helpers.formatter_factory { command = 'bean-format', args = { '-w', '80' }, to_stdin = true, }, } null_ls.register { bean_format } end null_ls.setup { sources = { null_ls.builtins.formatting.black.with { extra_args = { '--line-length', '100' } }, null_ls.builtins.formatting.stylua, null_ls.builtins.formatting.cbfmt, null_ls.builtins.diagnostics.shellcheck, null_ls.builtins.formatting.prettier, null_ls.builtins.code_actions.gitrebase, null_ls.builtins.hover.printenv, }, on_attach = on_attach, root_dir = require('null-ls.utils').root_pattern('.hg', '.git', 'stylua.toml'), } require('neodev').setup { override = function(root_dir, options) if root_dir:find 'chezmoi' then options.enabled = true options.runtime = true options.types = true options.plugins = true end end, } lsp.lua_ls.setup { lspconfig = { cmd = { 'lua-language-server' }, on_attach = on_attach, capabilities = capabilities, settings = { Lua = { completion = { callSnippet = 'Replace', }, }, }, }, } require('zk').setup { lsp = { on_attach = on_attach, capabilities = capabilities }, } vim.diagnostic.config { underline = true, update_in_insert = false, virtual_text = { severity = { min = vim.diagnostic.severity.WARN }, source = 'always' }, severity_sort = true, } vim.fn.sign_define('DiagnosticSignError', { texthl = 'DiagnosticSignError', linehl = '', numhl = '', text = '▎' }) vim.fn.sign_define('DiagnosticSignWarn', { texthl = 'DiagnosticSignWarn', linehl = '', numhl = '', text = '▎', }) vim.fn.sign_define('DiagnosticSignInfo', { texthl = 'DiagnosticSignInfo', linehl = '', numhl = '', text = '▎', }) vim.fn.sign_define('DiagnosticSignHint', { texthl = 'DiagnosticSignHint', linehl = '', numhl = '', text = '▎' }) -- Try importing local config local ok, localconfig = pcall(require, 'localconfig') if ok then localconfig.setup { on_attach = on_attach, capabilities = capabilities } end