maxwell/neovim.nix

492 lines
15 KiB
Nix
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

{ 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
fortran haskell
html css markdown
nix python
]))
# 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
--
-- 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("\\<CR>")'})
keymap('t', '<C-b>', '<C-\\><C-n>', {noremap=true}) -- Easier escape
keymap('n', '<C-b>', '<Nop>', {noremap=true}) --
keymap('n', '<C-w>-', ':split +term<CR>', {silent=true}) -- Tmux-like moves
keymap('n', '<C-w>|', ':vsplit +term<CR>', {silent=true}) --
keymap('n', '<C-w>t', ':tabnew +term<CR>', {silent=true}) --
keymap('n', '<C-w>c', ':quit<CR>', {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', '<C-p>', searchFiles, {silent=true}) -- Fuzzy search files
keymap('n', '<C-e>', searchCommands, {silent=true}) -- Fuzzy search command history
keymap('n', '<Leader>u', ':UndotreeToggle<CR>', {silent=true}) -- Toggle UndoTree
keymap('n', '<leader>l', listToggle, {silent=true}) -- Toggle Neomake errors
keymap('i', 'kj', '<ESC>', {noremap=true}) -- Exit with kj
keymap('n', 'o', 'o<ESC>', {noremap=true}) -- Add empty lines
keymap('n', 'O', 'O<ESC>', {noremap=true}) --
keymap('x', 'p', 'p:let @+=@0<CR>', {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', '<leader>gb', function() gitsigns.blame_line{full=true} end, {silent=true})
keymap('n', '<leader>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={'VLine', 'VLine'},
t={'Termin', 'Term'},
['']={'VBloc', '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 '<nixpkgs/nixos>' pkgs.neovim for testing
nixpkgs.overlays = lib.singleton (self: super: {
neovim = neovim-wrapped;
});
}