{ config, lib, pkgs, ... }: let frameline = pkgs.callPackage (pkgs.fetchFromGitea { domain = "maxwell.ydns.eu/git"; owner = "rnhmjoj"; repo = "nvim-frameline"; rev = "v0.1.0"; sha256 = "PrTSSoXbu+qtTsJUv81z+MuTUmB1RHLPEWFQQnu6+J8="; }) { }; plugins = with pkgs.vimPlugins; [ # UI undotree gitsigns-nvim frameline nvim-fzf # Syntax vim-pandoc-syntax nix-queries fortran-queries (nvim-treesitter.withPlugins (p: with p; [ bash fish c haskell html css markdown nix python lua ])) # Misc vim-fugitive supertab neomake auto-pairs plenary-nvim ]; pack = pkgs.linkFarm "neovim-plugins" (map (pkg: { name = "pack/${pkg.name}/start/${pkg.name}"; path = toString pkg; }) (lib.concatMap (p: [p] ++ p.dependencies or []) plugins)); neovim-wrapped = pkgs.runCommand "${pkgs.neovim-unwrapped.name}" { nativeBuildInputs = [ pkgs.makeWrapper ]; } '' mkdir -p "$out" makeWrapper '${pkgs.neovim-unwrapped}/bin/nvim' "$out/bin/nvim" \ --add-flags "-u ${conf}" ''; nix-queries = pkgs.writeTextDir "/queries/nix/injections.scm" '' ;; extends ; /*language*/ highlight (too slow) ; ((comment) @language ; [ ; (string_expression (string_fragment) @content) ; (indented_string_expression (string_fragment) @content) ; ] ; (#gsub! @language "/%*%s*([%w%p]+)%s*%*/" "%1")) ; @combined ; writeText highlight (apply_expression function: (apply_expression function: (_) @_func argument: (string_expression (string_fragment) @language)) argument: [(string_expression (string_fragment) @content) (indented_string_expression (string_fragment) @content)] (#match? @_func "(^|\\.)writeText(Dir)?$") (#gsub! @language ".*%.(.*)" "%1")) @combined ''; fortran-queries = pkgs.writeTextDir "/queries/fortran/highlights.scm" '' ;; extends (end_block_construct_statement) @keyword (implicit_statement) @keyword ''; conf = pkgs.writeText "init.lua" '' local opt = vim.opt local cmd = vim.api.nvim_command local keymap = vim.keymap.set local autocmd = vim.api.nvim_create_autocmd -- Load plugins opt.packpath = ${builtins.toJSON pack} opt.runtimepath:prepend(${builtins.toJSON pack}) -- -- Options -- local cache = os.getenv('XDG_CACHE_HOME')..'/nvim' opt.directory = cache..'/tmp' opt.backupdir = cache..'/tmp' opt.shadafile = cache..'/shada' opt.undodir = cache..'/undo' opt.hidden = true -- Hide buffers opt.mouse = 'a' -- Enable mouse support opt.ignorecase = true -- Case insensitive search... opt.smartcase = true -- ...for lowercase terms opt.splitkeep = 'screen' -- keep text still when splitting opt.fsync = true -- Sync writes opt.swapfile = false -- Disable swap files opt.writebackup = true -- Backup file before overwriting... opt.backup = false -- ...but delete it on success opt.undofile = true -- Store all changes opt.modeline = false -- Disable for Security opt.showmatch = true -- Highlight matched parenthesis opt.clipboard = 'unnamedplus' -- Yank to clipboard -- Files to ignore opt.wildignore = { '*.so', '*.hi', '*.a', '*.la', '*.mod', '*/__pycache__/*', '*/dist/*', '*/result/*', '*/.git/*' } opt.shiftwidth = 2 -- Tabs opt.tabstop = 2 -- opt.expandtab = true -- opt.number = true -- Line numbering opt.smartindent = true -- Indentation opt.showmode = false -- Disable printing of mode changes opt.ruler = false -- Already in statusline opt.fillchars = { eob=' ', -- Hide ~ on empty lines vert='│', -- make vertical split sign better fold=' ', -- Hide . in fold markers } opt.foldmethod = "expr" -- Folding opt.foldexpr = 'nvim_treesitter#foldexpr()' -- opt.foldlevel = 99 -- open by default opt.foldopen:remove("search") -- don't open when searching -- -- OSC 52 clipboard -- -- if os.getenv('DISPLAY') == nil then function copy(sel) return function(lines, _) local data = vim.fn.system([[base64 -w0]], lines) io.stdout:write('\027]52;'..sel..';'..data..'\a') vim.g.fallback_clip = lines end end function paste(sel) -- currently impossible to implement return function() return {vim.g.fallback_clip, ""} end end vim.g.clipboard = { name='ssh', copy={['*']=copy's', ['+']=copy'c'}, paste={['*']=paste's', ['+']=paste'c'}, } end -- -- Terminal mode -- -- Hide some UI elements autocmd('TermOpen', {pattern='*', command='setlocal nonumber'}) autocmd('TermEnter', {pattern='*', command='set noshowcmd'}) autocmd('TermLeave', {pattern='*', command='set showcmd'}) autocmd('VimResized', {pattern='*', command='wincmd ='}) -- Exit without confirmation autocmd('TermClose', {pattern='*', command='call feedkeys("\\")'}) keymap('t', '', '', {noremap=true}) -- Easier escape keymap('n', '', '', {noremap=true}) -- keymap('n', '-', ':split +term', {silent=true}) -- Tmux-like moves keymap('n', '|', ':vsplit +term', {silent=true}) -- keymap('n', 't', ':tabnew +term', {silent=true}) -- keymap('n', 'c', ':quit', {silent=true}) -- -- -- Keybindings -- vim.g.mapleader = ',' function listToggle() for _, win in ipairs(vim.fn.getwininfo()) do if win.loclist == 1 then return cmd('lclose') end end cmd('lopen') end fzf = require'fzf' function searchFiles() local query = [[\( -name .git -o -name __pycache__ -o -path ./dist -o -path ./build \) -prune -o -type f]] coroutine.wrap(function() res = fzf.fzf('find '..query, "", {border='none'}) cmd('edit '..res[1]) end)() end function searchCommands() coroutine.wrap(function() local history = {} for i = 1, vim.fn.histnr("cmd") do history[i] = vim.fn.histget("cmd", i) end res = fzf.fzf(history, "", {border='none'}) vim.fn.feedkeys(':'..res[1], 't') end)() end keymap('n', '', searchFiles, {silent=true}) -- Fuzzy search files keymap('n', '', searchCommands, {silent=true}) -- Fuzzy search command history keymap('n', 'u', ':UndotreeToggle', {silent=true}) -- Toggle UndoTree keymap('n', 'l', listToggle, {silent=true}) -- Toggle Neomake errors keymap('i', 'kj', '', {noremap=true}) -- Exit with kj keymap('n', 'o', 'o', {noremap=true}) -- Add empty lines keymap('n', 'O', 'O', {noremap=true}) -- keymap('x', 'p', 'p:let @+=@0', {noremap=true, silent=true}) -- Keep selection after p keymap('c', 'w!!', 'w !sudo tee >/dev/null %', {silent=true}) -- Save with sudo -- -- Colors -- opt.bg = 'light' -- Use dark colors function color(group, args) vim.api.nvim_set_hl(0, group, args) end -- Source code color('Cursor', {ctermfg=14}) color('Keyword', {ctermfg=04}) color('Define', {ctermfg=03}) color('Type', {ctermfg=06}) color('Identifier', {ctermfg=13}) color('Constant', {ctermfg=03}) color('Function', {ctermfg=02}) color('Include', {ctermfg=04}) color('Statement', {ctermfg=11}) color('String', {ctermfg=03}) color('Number', {ctermfg=04}) color('Comment', {ctermfg=07}) color('SpecialComment', {ctermfg=15}) color('Operator', {ctermfg='none'}) color('Conceal', {ctermfg='none', ctermbg='none'}) -- Text color('Title', {ctermfg=4}) color('Special', {ctermfg=12}) color('Delimiter', {ctermfg=1}) color('PandocReferenceURL', {ctermfg=3}) color('PandocCiteKey', {ctermfg=6}) color('PandocTableDelims', {ctermfg=8}) color('texBeginEndName', {ctermfg=2}) color('Error', {ctermfg='none', ctermbg='none', underline=true}) -- Editor UI color('NonText', {ctermfg=0}) color('LineNr', {ctermfg=8}) color('Pmenu', {ctermfg=12, ctermbg=0}) color('Folded', {ctermfg=7, ctermbg=0, cterm={}}) color('VertSplit', {ctermfg=8, cterm={}}) color('FoldColumn', {ctermfg='none', cterm={}}) color('Visual', {ctermfg='none', ctermbg=0}) color('Search', {ctermfg='none', ctermbg=0}) -- Diff mode color('DiffAdd', {ctermfg=2, ctermbg=0, underline=true}) color('DiffChange', {ctermfg=3, ctermbg=0, underline=true}) color('DiffText', {ctermfg=1, ctermbg=0, underline=true}) color('DiffDelete', {ctermfg=0, ctermbg=1, underline=true}) -- Spelling color('SpellBad', {ctermfg=1, ctermbg=0, underline=true}) color('SpellCap', {ctermfg=3, underline=true}) -- Neomake color('NeomakeWarning', {ctermfg=3, underline=true}) color('ErrorMsg', {ctermfg=1, ctermbg='none'}) color('WarningMsg', {ctermfg=3}) -- Git signs color('SignColumn', {ctermfg=1, ctermbg='none', cterm={}}) color('GitAdd', {ctermfg=2}) color('GitChange', {ctermfg=3}) color('GitDelete', {ctermfg=1}) -- Statusline color('StatusLine', {ctermfg=8, ctermbg=0, cterm={}}) color('StatusLineNC', {ctermfg=4, ctermbg=0, cterm={}}) color('User1', {ctermfg=8, ctermbg=0}) -- base color('User2', {ctermfg=3, ctermbg=0}) -- location color('StatusLineErr', {ctermfg=0, ctermbg=1}) color('StatusLineWarn', {ctermfg=0, ctermbg=3}) color('TablineTab', {ctermfg=8, ctermbg=0}) color('TablineTabCur', {ctermfg=7, ctermbg=8}) color('ModeNormal', {ctermfg=7, ctermbg=14}) color('ModeInsert', {ctermfg=7, ctermbg=1}) color('ModeCommand', {ctermfg=7, ctermbg=4}) color('ModeVisual', {ctermfg=7, ctermbg=3}) color('ModeVLine', {ctermfg=7, ctermbg=11}) color('ModeVBloc', {ctermfg=7, ctermbg=11}) color('ModeTerm', {ctermfg=7, ctermbg=2}) -- -- Plugin options -- -- Neomake vim.call('neomake#configure#automake', 'nwr', 750) vim.g.neomake_warning_sign = {text='W→', texthl='WarningMsg'} vim.g.neomake_error_sign = {text='E→', texthl='ErrorMsg'} vim.g.neomake_highlight_lines = 1 vim.g.neomake_virtualtext_current_error = 0 vim.g.neomake_fortran_gfortran_args = {'-fsyntax-only', '-Wall', '-Wextra', '-Jbuild/obj'} -- Pandoc Markdown autocmd({'BufNewFile', 'BufFilePre', 'BufRead'}, {pattern='*.md', command='set filetype=markdown.pandoc'}) -- Git signs gitsigns = require'gitsigns' gitsigns.setup{signs={ add = {hl='GitAdd' , text='+'}, change = {hl='GitChange', text='δ'}, delete = {hl='GitDelete', text='-'}, topdelete = {hl='GitDelete', text='‾'}, changedelete = {hl='GitChange', text='~'}} } keymap('n', 'gb', function() gitsigns.blame_line{full=true} end, {silent=true}) keymap('n', 'gp', function() gitsigns.preview_hunk() end, {silent=true}) -- Tree-sitter require'nvim-treesitter.configs'.setup{ highlight={enable=true}, indent={enable=true}, } -- Non built-in filetypes autocmd({'BufNewFile', 'BufRead'}, {pattern='*.nix', command='setlocal filetype=nix'}) -- -- Statusline -- -- local frameline = require 'frameline' local utils = frameline.utils function mode(win, buf) if not win.is_active then return end local k = vim.api.nvim_get_mode().mode local modes = { n={'Normal', 'Normal'}, i={'Insert', 'Insert'}, R={'Replas', 'Insert'}, v={'Visual', 'Visual'}, V={'V⋅Line', 'VLine'}, t={'Termin', 'Term'}, ['']={'V⋅Bloc', 'VBloc'} } return utils.highlight('Mode'..modes[k][2], ' '..modes[k][1]..' ') end function branch(_, buf) local head = vim.fn.FugitiveHead() if buf.modifiable and head ~= "" then return '⚑ '..head end end function filename(_, buf) local delta = "" if buf.modified then delta = 'Δ' end if not buf.modifiable then delta = '∇' end local fname = buf.name ~= "" and utils.filename or 'new-file' return delta..fname end function neomake() local res, msg = vim.call('neomake#statusline#LoclistCounts'), "" if res.E then msg = msg..utils.highlight('StatusLineErr', ' '..res.E..'E ') end if res.W then msg = msg..utils.highlight('StatusLineWarn', ' '..res.W..'W ') end return msg end function readonly(_, buf) if buf.modifiable and buf.readonly then return '∅' end end function encoding(_, buf) return buf.fileencoding ~= "" and buf.fileencoding or nil end function filetype(_, buf) return buf.filetype ~= "" and buf.filetype or 'no ft' end -- Tabline frameline.setup_tabline(function() local segments = {} local api = vim.api local color = '%#StatusLine#' -- Tabs local current = api.nvim_get_current_tabpage() local label = ' %d %s ' for i, tab in pairs(api.nvim_list_tabpages()) do -- tab -> active win -> active buf -> name local active_buf = api.nvim_win_get_buf(api.nvim_tabpage_get_win(tab)) local name = api.nvim_buf_get_name(active_buf) name = vim.fn.fnamemodify(name, ':t') -- filename only local group = tab == current and 'TablineTabCur' or 'TablineTab' table.insert(segments, utils.highlight(group, label:format(i, name))) end table.insert(segments, color) table.insert(segments, utils.split) -- Current date table.insert(segments, vim.fn.strftime("%a %H:%M")) -- Battery level local level = io.popen('cat /sys/class/power_supply/BAT*/capacity'):read() local symbol = ' ∘ '..utils.highlight('User2', '⚡')..color local battery = level and symbol..level..'%% ' or "" table.insert(segments, battery) return table.concat(segments) end) -- Statusline frameline.setup_statusline(function() local segments = {} -- Left section table.insert(segments, utils.subsection{items={mode}}) table.insert(segments, utils.subsection{ separator=' → ', items={branch, filename, readonly}, }) table.insert(segments, utils.split) -- Right section table.insert(segments, utils.subsection{items={neomake}}) table.insert(segments, utils.subsection{ user=2, separator=':', stop=' ', items={utils.line_number, utils.column_number}, }) table.insert(segments, utils.subsection{ user=1, separator=' ∘ ', items={utils.percent, encoding, filetype} }) return segments end) ''; in { # nix build -f '' pkgs.neovim for testing nixpkgs.overlays = lib.singleton (self: super: { neovim = neovim-wrapped; }); }