From a0bcd443b7fea11ff259585d4459f259d4fb0266 Mon Sep 17 00:00:00 2001 From: Christoph Heiss Date: Tue, 18 Apr 2023 19:39:42 +0200 Subject: [PATCH] home-manager: Add neovim configuration Signed-off-by: Christoph Heiss --- extra/nvim/init.lua | 4 + extra/nvim/lua/autocmds.lua | 83 +++++++++ extra/nvim/lua/basic.lua | 88 +++++++++ extra/nvim/lua/mappings.lua | 35 ++++ extra/nvim/lua/plugins.lua | 311 ++++++++++++++++++++++++++++++++ system/home-manager/desktop.nix | 21 ++- 6 files changed, 537 insertions(+), 5 deletions(-) create mode 100644 extra/nvim/init.lua create mode 100644 extra/nvim/lua/autocmds.lua create mode 100644 extra/nvim/lua/basic.lua create mode 100644 extra/nvim/lua/mappings.lua create mode 100644 extra/nvim/lua/plugins.lua diff --git a/extra/nvim/init.lua b/extra/nvim/init.lua new file mode 100644 index 0000000..b9427e2 --- /dev/null +++ b/extra/nvim/init.lua @@ -0,0 +1,4 @@ +require('basic') +require('plugins') +require('autocmds') +require('mappings') diff --git a/extra/nvim/lua/autocmds.lua b/extra/nvim/lua/autocmds.lua new file mode 100644 index 0000000..b9966c9 --- /dev/null +++ b/extra/nvim/lua/autocmds.lua @@ -0,0 +1,83 @@ +vim.api.nvim_create_autocmd({ 'FileType' }, { + pattern = { 'lua', 'html', 'nix' }, + callback = function() + vim.opt_local.tabstop = 2 + vim.opt_local.shiftwidth = 2 + end, +}) + +vim.api.nvim_create_autocmd({ 'FileType' }, { + pattern = { 'gitsendemail', 'gitcommit', 'mail' }, + callback = function() + vim.opt_local.textwidth = 72 + vim.opt_local.colorcolumn = '73' + end, +}) + +vim.api.nvim_create_autocmd({ 'FileType' }, { + pattern = 'make', + callback = function() + vim.opt_local.expandtab = false + end, +}) + +vim.api.nvim_create_autocmd({ 'BufWritePre' }, { + pattern = '*', + callback = function() + if vim.bo.ft ~= 'diff' and vim.bo.ft ~= 'markdown' then + vim.cmd([[%s/\s\+$//e]]) + end + end, +}) + +vim.api.nvim_create_autocmd({ 'TermEnter' }, { + pattern = '*', + callback = function() + vim.opt_local.scrolloff = 0 + end, +}) + +vim.api.nvim_create_autocmd({ 'TermLeave' }, { + pattern = '*', + callback = function() + vim.opt_local.scrolloff = 5 + end, +}) + +vim.api.nvim_create_autocmd({ 'BufReadPost' }, { + pattern = '*', + callback = function() + local line = vim.fn.line('\'"') + if line > 1 and line <= vim.fn.line('$') then + vim.cmd['normal!']('g`"') + end + end, +}) + +-- Automatically switch between absolute and relative line numbers when +-- entering insert mode +local numbertoggle_group = vim.api.nvim_create_augroup('numbertoggle', {}) + +vim.api.nvim_create_autocmd( + { 'BufEnter', 'FocusGained', 'InsertLeave', 'WinEnter' }, + { + pattern = '*', + group = numbertoggle_group, + callback = function() + if vim.fn.mode() ~= 'i' then + vim.opt.relativenumber = true + end + end, + } +) + +vim.api.nvim_create_autocmd( + { 'BufLeave', 'FocusLost', 'InsertEnter', 'WinLeave' }, + { + pattern = '*', + group = numbertoggle_group, + callback = function() + vim.opt.relativenumber = false + end, + } +) diff --git a/extra/nvim/lua/basic.lua b/extra/nvim/lua/basic.lua new file mode 100644 index 0000000..9f7f5a2 --- /dev/null +++ b/extra/nvim/lua/basic.lua @@ -0,0 +1,88 @@ +-- Basic setup +vim.g.mapleader = ',' + +vim.cmd.filetype('plugin', 'indent', 'on') +vim.cmd.syntax('on') + +vim.opt.ruler = true +vim.opt.number = true +vim.opt.laststatus = 2 +vim.opt.colorcolumn = '80,100,120' +vim.opt.autoread = true +vim.opt.showmode = false + +vim.opt.shell = vim.env.SHELL or '/bin/sh' +vim.opt.clipboard:append('unnamedplus') + +-- Modelines +vim.opt.modeline = true +vim.opt.modelines = 4 +-- https://github.com/numirias/security/blob/master/doc/2019-06-04_ace-vim-neovim.md +vim.opt.modelineexpr = false + +-- Always use UTF-8 +vim.opt.encoding = 'utf-8' +vim.opt.fileencoding = 'utf-8' +vim.opt.fileencodings = 'utf-8' +vim.opt.fileformats = 'unix,dos,mac' + +-- Fix backspace indent +vim.opt.backspace = 'indent,eol,start' + +-- Default indent settings +vim.opt.tabstop = 4 +vim.opt.softtabstop = 0 +vim.opt.shiftwidth = 4 +vim.opt.expandtab = true + +-- Hidden buffers +vim.opt.hidden = true + +-- Searching options +vim.opt.hlsearch = true +vim.opt.incsearch = true +vim.opt.ignorecase = true +vim.opt.smartcase = true + +-- Disable mouse support +vim.opt.mouse = '' + +-- Disable netrw +vim.g.loaded_netrwPlugin = 0 + +-- Wildmenu +vim.opt.wildmenu = true +vim.opt.wildmode = 'list:longest,list:full' +vim.opt.wildignore:append(table.concat({ + '*.o', + '*.so', + '*.swp', + '*.zip', + '*.tar', + '*.db', + '*.sqlite', + '*.obj', + '*.rbc', + '*.pyc', + '*.pdf', + '*.webp', + '*.png', + '*.jpg', + '*/.git/*', + '*/__pycache__/*', + '*/node_modules/*', + '*/tmp/*', +}, ',')) + +-- Host-specific config +if vim.fn.hostname() == 'maui' then + vim.opt.tabstop = 8 + vim.opt.softtabstop = 4 + vim.opt.shiftwidth = 4 + vim.opt.textwidth = 100 + vim.opt.noexpandtab = true + vim.opt.nocopyindent = true + vim.opt.autoindent = true + vim.opt.smartindent = true + vim.opt.cinoptions = '(0,u0,U0' +end diff --git a/extra/nvim/lua/mappings.lua b/extra/nvim/lua/mappings.lua new file mode 100644 index 0000000..d6f1ef1 --- /dev/null +++ b/extra/nvim/lua/mappings.lua @@ -0,0 +1,35 @@ +-- Dvorak layout remaps +vim.keymap.set('', 'd', '') +vim.keymap.set('', 'h', '') +vim.keymap.set('', 't', '') +vim.keymap.set('', 'n', '') +vim.keymap.set('', 'e', 'd') +vim.keymap.set('', '', '') + +-- Split +-- Configure like tmux, which reverses the meaning of v- and h-splits +vim.keymap.set('', 'v', 'split') +vim.keymap.set('', 'b', 'vsplit') + +-- Window navigation +vim.keymap.set('', '', 'h') +vim.keymap.set('', '', 'j') +vim.keymap.set('', '', 'k') +vim.keymap.set('', '', 'l') + +-- Search +vim.keymap.set('n', '', 'nohlsearch') +vim.keymap.set('n', 'l', 'nzzzv') +vim.keymap.set('n', 'L', 'Nzzzv') + +-- Maintain visual blocks when shifting +vim.keymap.set('v', '<', '', '>gv') + +-- Move visual blocks down/up +vim.keymap.set('v', 'H', ":move '>+1gv=gv") +vim.keymap.set('v', 'T', ":move '<-2gv=gv") + +-- Quitting +vim.keymap.set('n', 'w', 'w') +vim.keymap.set('n', 'q', 'wqall') diff --git a/extra/nvim/lua/plugins.lua b/extra/nvim/lua/plugins.lua new file mode 100644 index 0000000..1d20168 --- /dev/null +++ b/extra/nvim/lua/plugins.lua @@ -0,0 +1,311 @@ +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', + lazypath, + }) +end + +vim.opt.rtp:prepend(lazypath) +vim.g.loaded_node_provider = 0 +vim.g.loaded_perl_provider = 0 +vim.g.loaded_ruby_provider = 0 + +require('lazy').setup({ + { + 'dracula/vim', + name = 'dracula', + priority = 100, + config = function() + vim.g.dracula_colorterm = 0 + vim.cmd.colorscheme('dracula') + end, + }, + + { + 'itchyny/lightline.vim', + config = function() + vim.g.lightline = { + enable = { + tabline = 0, + }, + colorscheme = 'one', + active = { + left = { + { 'mode', 'paste' }, + { 'readonly', 'filename', 'modified' }, + { 'gitbranch' }, + }, + }, + component = { + gitbranch = '\226\142\135 %{FugitiveHead()}', + }, + } + end, + }, + + { + 'romgrk/barbar.nvim', + init = function() + vim.g.barbar_auto_setup = false + vim.keymap.set('n', '', 'BufferNext') + vim.keymap.set('n', '', 'BufferPrevious') + vim.keymap.set('n', '', 'BufferPick') + vim.keymap.set('n', '', 'BufferPickDelete') + vim.keymap.set('n', 'c', 'BufferClose') + vim.keymap.set('n', '', 'tabnew') + end, + opts = { + clickable = false, + icons = { + button = '', + filetype = { enabled = false }, + pinned = { + button = '\240\159\147\140', + filename = true, + }, + }, + }, + }, + + { + 'tpope/vim-fugitive', + init = function() + vim.keymap.set('n', 'ga', 'Git add -p') + vim.keymap.set('n', 'gb', 'Git blame') + vim.keymap.set('n', 'gd', 'Git diff') + vim.keymap.set('n', 'gl', 'Git log --oneline') + end, + }, + + { 'tpope/vim-commentary' }, + { 'Raimondi/delimitMate' }, + { 'editorconfig/editorconfig-vim', submodules = false }, + { 'easymotion/vim-easymotion' }, + + { + 'airblade/vim-gitgutter', + init = function() + vim.keymap.set('n', 'ht', '(GitGutterPrevHunk)') + vim.keymap.set('n', 'hh', '(GitGutterNextHunk)') + vim.keymap.set('n', 'hd', '(GitGutterPreviewHunk)') + end, + }, + + { + 'folke/persistence.nvim', + tag = 'v1.1.0', + opts = { + options = { 'buffers', 'curdir', 'tabpages', 'winsize', 'globals' }, + pre_save = function() + vim.api.nvim_exec_autocmds('User', { pattern = 'SessionSavePre' }) + end, + }, + }, + + { + 'junegunn/fzf.vim', + dependencies = { 'junegunn/fzf' }, + init = function() + if vim.fn.executable('fzf') == 0 then + vim.notify( + '`fzf` not found in PATH, all fuzzy-find commands disabled', + vim.log.levels.WARN + ) + return + end + + vim.keymap.set('n', 'b', 'Buffers') + vim.keymap.set('n', 'y', 'History:') + vim.keymap.set('n', 's', 'BLines') + + vim.keymap.set( + 'n', + 'u', + [[call fzf#vim#files('', fzf#vim#with_preview({'sink': 'tabedit'}))]] + ) + vim.keymap.set( + 'n', + 'e', + [[call fzf#vim#gitfiles('', fzf#vim#with_preview({'sink': 'tabedit'}))]] + ) + vim.keymap.set( + 'n', + 'm', + [[call fzf#vim#gitfiles('?', fzf#vim#with_preview({'sink': 'tabedit'}))]] + ) + + if vim.fn.executable('rg') == 0 then + vim.notify( + '`rg` not found in PATH, fuzzy grep and optimized file search disabled', + vim.log.levels.WARN + ) + else + vim.env.FZF_DEFAULT_COMMAND = + 'rg --files --hidden --follow --glob "!.git/*"' + vim.keymap.set('n', 'f', 'Rg') + end + end, + }, + + { + 'Yggdroot/indentLine', + config = function() + vim.g.indentLine_concealcursor = '' + vim.g.indentLine_char = '┆' + vim.g.indentLine_faster = 1 + end, + }, + + -- HTML + { 'hail2u/vim-css3-syntax', ft = { 'css', 'sass', 'scss' } }, + { 'gko/vim-coloresque', ft = { 'html', 'sass', 'scss', 'less', 'yaml' } }, + + -- Perl + { 'vim-perl/vim-perl', ft = 'perl' }, + + -- Rust + { 'rust-lang/rust.vim', ft = 'rust' }, + + -- Nix + { 'LnL7/vim-nix', ft = 'nix' }, + + -- Autocomplete + { + 'hrsh7th/nvim-cmp', + dependencies = { + 'neovim/nvim-lspconfig', + 'hrsh7th/cmp-nvim-lsp', + 'hrsh7th/cmp-buffer', + 'hrsh7th/cmp-path', + 'hrsh7th/cmp-cmdline', + 'hrsh7th/cmp-nvim-lsp-signature-help', + 'kdheepak/cmp-latex-symbols', + 'saadparwaiz1/cmp_luasnip', + 'rafamadriz/friendly-snippets', + { + 'L3MON4D3/LuaSnip', + tag = 'v1.2.1', + submodules = false, + config = function() + require('luasnip.loaders.from_vscode').lazy_load() + end, + }, + { 'j-hui/fidget.nvim', config = true }, + { + 'f3fora/cmp-spell', + config = function() + vim.opt.spell = false + vim.opt.spelllang = 'en_us' + vim.keymap.set('n', 'p', 'set spell!') + end, + }, + { + 'jose-elias-alvarez/null-ls.nvim', + config = function() + local null_ls = require('null-ls') + + null_ls.setup({ + sources = { + -- null_ls.builtins.formatting.stylua, + }, + }) + end, + }, + { + 'simrat39/rust-tools.nvim', + ft = 'rust', + opts = { + tools = { + inlay_hints = { + auto = true, + }, + }, + server = { + settings = { + ['rust-analyzer'] = { + checkOnSave = { + command = 'clippy', + }, + }, + }, + }, + }, + }, + { + 'saecki/crates.nvim', + tag = 'v0.3.0', + dependencies = { 'nvim-lua/plenary.nvim' }, + config = function() + local crates = require('crates') + + vim.keymap.set('n', 'rv', crates.show_versions_popup) + vim.keymap.set('n', 'rf', crates.show_features_popup) + vim.keymap.set('n', 'ri', crates.open_crates_io) + + crates.setup({ + popup = { + autofocus = true, + }, + }) + end, + }, + }, + config = function() + local cmp = require('cmp') + + cmp.setup({ + snippet = { + expand = function(args) + require('luasnip').lsp_expand(args.body) + end, + }, + window = { + completion = cmp.config.window.bordered(), + documentation = cmp.config.window.bordered(), + }, + mapping = cmp.mapping.preset.insert({ + [''] = cmp.mapping.scroll_docs(-5), + [''] = cmp.mapping.scroll_docs(5), + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.complete(), + [''] = cmp.mapping.abort(), + [''] = cmp.mapping.confirm({ select = true }), + }), + sources = cmp.config.sources({ + { name = 'nvim_lsp' }, + { name = 'nvim_lsp_signature_help' }, + { name = 'luasnip' }, + { name = 'crates' }, + }, { + { name = 'buffer' }, + }), + }) + + cmp.setup.cmdline(':', { + mapping = cmp.mapping.preset.cmdline(), + sources = cmp.config.sources({ + { name = 'path' }, + }, { + { name = 'cmdline' }, + }), + }) + + vim.opt.completeopt = 'menuone,noinsert,noselect' + vim.opt.shortmess:append('c') + end, + }, +}) + +-- Fix color scheme collision +vim.cmd.highlight('comment', 'ctermfg=7') + +-- Bring modified buffers to attention +vim.cmd['highlight!']('BufferInactiveMod', 'cterm=bold') +vim.cmd['highlight!']('BufferCurrentMod', 'cterm=bold') diff --git a/system/home-manager/desktop.nix b/system/home-manager/desktop.nix index d23116e..796c135 100644 --- a/system/home-manager/desktop.nix +++ b/system/home-manager/desktop.nix @@ -5,6 +5,11 @@ let package = pkgs.apple-cursor; name = "macOS-Monterey"; }; + nvimLuaModuleNames = [ "basic" "plugins" "autocmds" "mappings" ]; + nvimLuaModules = builtins.listToAttrs (map (m: { + name = "nvim/lua/${m}.lua"; + value.source = ../../extra/nvim/lua + "/${m}.lua"; + }) nvimLuaModuleNames); in { home-manager.users.christoph = { imports = [ ./email.nix ./sway.nix ./terminal.nix ]; @@ -45,6 +50,9 @@ in { }; }; + # Needed by the luasnip plugin + programs.neovim.extraLuaPackages = luaPkgs: with luaPkgs; [ jsregexp ]; + programs.ssh = { enable = true; matchBlocks."*" = { @@ -90,10 +98,13 @@ in { pinentryFlavor = "tty"; }; - xdg.configFile."latexmk/latexmkrc".text = '' - $pdf_previewer = '${pkgs.zathura}/bin/zathura'; - $latex = 'latex -interaction=nonstopmode'; - $pdflatex = 'pdflatex -interaction=nonstopmode'; - ''; + xdg.configFile = { + "latexmk/latexmkrc".text = '' + $pdf_previewer = '${pkgs.zathura}/bin/zathura'; + $latex = 'latex -interaction=nonstopmode'; + $pdflatex = 'pdflatex -interaction=nonstopmode'; + ''; + "nvim/init.lua".source = ../../extra/nvim/init.lua; + } // nvimLuaModules; }; }