diff options
Diffstat (limited to '.config/nvim/lua')
| -rw-r--r-- | .config/nvim/lua/init/00-bootstrap.lua | 4 | ||||
| -rw-r--r-- | .config/nvim/lua/init/01-options.lua | 153 | ||||
| -rw-r--r-- | .config/nvim/lua/init/02-commands.lua | 47 | ||||
| -rw-r--r-- | .config/nvim/lua/init/03-autocmds.lua | 55 | ||||
| -rw-r--r-- | .config/nvim/lua/init/04-mappings.lua | 563 | ||||
| -rw-r--r-- | .config/nvim/lua/init/05-appearance.lua | 227 | ||||
| -rw-r--r-- | .config/nvim/lua/init/06-plugins.lua | 30 |
7 files changed, 1079 insertions, 0 deletions
diff --git a/.config/nvim/lua/init/00-bootstrap.lua b/.config/nvim/lua/init/00-bootstrap.lua new file mode 100644 index 0000000..b65a4f0 --- /dev/null +++ b/.config/nvim/lua/init/00-bootstrap.lua @@ -0,0 +1,4 @@ +require('vimrc.my_env').mkdir() +_G.vimrc = require('vimrc') +vim.cmd('language messages C') +vim.cmd('language time C') diff --git a/.config/nvim/lua/init/01-options.lua b/.config/nvim/lua/init/01-options.lua new file mode 100644 index 0000000..c229251 --- /dev/null +++ b/.config/nvim/lua/init/01-options.lua @@ -0,0 +1,153 @@ +local O = vim.o +local OPT = vim.opt +local my_env = require('vimrc.my_env') + + +-- Moving around, searching and patterns {{{1 + +O.wrapscan = false +O.ignorecase = true +O.smartcase = true + + +-- Displaying text {{{1 + +O.scrolloff = 7 +O.linebreak = true +O.breakindent = true +OPT.breakindentopt:append('sbr') +O.showbreak = '> ' +O.sidescrolloff = 20 +OPT.fillchars = { + vert = ' ', + fold = ' ', + diff = ' ', +} +O.cmdheight = 1 +O.list = true +OPT.listchars = { + eol = '\xc2\xac', -- u00ac + tab = '\xe2\x96\xb8 ', -- u25b8 + trail = '\xc2\xb7', -- u00b7 + extends = '\xc2\xbb', -- u00bb + precedes = '\xc2\xab', -- u00ab +} +O.concealcursor = 'cnv' + + +-- Syntax, highlighting and spelling {{{1 + +O.background = 'dark' +O.synmaxcol = 500 +O.hlsearch = true +O.termguicolors = true +O.colorcolumn = '+1' + + +-- Multiple windows {{{1 + +O.winminheight = 0 +O.hidden = true +O.switchbuf = 'usetab' + + +-- Multiple tabpages {{{1 + +O.showtabline = 2 + + +-- Terminal {{{1 + +O.title = false + + +-- Using the mouse {{{1 + +O.mouse = '' + + +-- Messages and info {{{1 + +OPT.shortmess:append('asIc') +O.showmode = false +O.report = 999 +O.confirm = true + + +-- Selecting text {{{1 + +O.clipboard = 'unnamed' + + +-- Editing text {{{1 + +O.undofile = true +O.textwidth = 0 +OPT.completeopt:remove('preview') +O.pumheight = 10 +OPT.matchpairs:append('<:>') +O.joinspaces = false +OPT.nrformats:append('unsigned') + + +-- Tabs and indenting {{{1 +-- Note: you should also set them for each file types. +-- These following settings are used for unknown file types. + +O.tabstop = 4 +O.shiftwidth = 4 +O.softtabstop = 4 +O.expandtab = true +O.smartindent = true +O.copyindent = true +O.preserveindent = true + + +-- Folding {{{1 + +O.foldlevelstart = 0 +OPT.foldopen:append('insert') +O.foldmethod = 'marker' + + +-- Diff mode {{{1 + +OPT.diffopt:append('vertical') +OPT.diffopt:append('foldcolumn:3') + + +-- Mapping {{{1 + +O.maxmapdepth = 10 +O.timeout = false + + +-- Reading and writing files {{{1 + +O.fixendofline = false +O.fileformats = 'unix,dos' +O.backup = true +O.backupdir = my_env.backup_dir +O.autowrite = true + + +-- Command line editing {{{1 + +OPT.wildignore:append('*.o') +OPT.wildignore:append('*.obj') +OPT.wildignore:append('*.lib') +O.wildignorecase = true + + +-- Executing external commands {{{1 + +O.shell = 'zsh' +O.keywordprg = '' + + +-- Encoding {{{1 + +O.fileencodings = 'utf-8,cp932,euc-jp' + + +-- Misc. {{{1 diff --git a/.config/nvim/lua/init/02-commands.lua b/.config/nvim/lua/init/02-commands.lua new file mode 100644 index 0000000..541e8da --- /dev/null +++ b/.config/nvim/lua/init/02-commands.lua @@ -0,0 +1,47 @@ +local F = vim.fn +local C = vim.api.nvim_create_user_command + + +-- :Reverse {{{1 + +-- Reverse a selected range in line-wise. +-- Note: directly calling `g/^/m` will overwrite the current search pattern with +-- '^' and highlight it, which is not expected. +-- :h :keeppatterns +C( + 'Reverse', + 'keeppatterns <line1>,<line2>g/^/m<line1>-1', + { + desc = 'Reverse lines', + bar = true, + range = '%', + } +) + + +-- :SmartTabEdit {{{1 + +-- If the current buffer is empty, open a file with the current window; +-- otherwise open a new tab. +C( + 'SmartTabEdit', + function(opts) + local is_empty_buffer = ( + F.bufname() == '' + and not vim.bo.modified + and F.line('$') <= 1 + and F.getline('.') == '' + ) + + if is_empty_buffer then + vim.cmd(mods .. ' edit ' .. args) + else + vim.cmd(mods .. ' tabedit ' .. args) + end + end, + { + desc = 'Smartly open a file', + nargs = '*', + complete = 'file', + } +) diff --git a/.config/nvim/lua/init/03-autocmds.lua b/.config/nvim/lua/init/03-autocmds.lua new file mode 100644 index 0000000..92e4344 --- /dev/null +++ b/.config/nvim/lua/init/03-autocmds.lua @@ -0,0 +1,55 @@ +local F = vim.fn +local O = vim.o +local vimrc = require('vimrc') +local A = vimrc.autocmd + + +vimrc.create_augroup_for_vimrc() + + +-- Auto-resize windows when Vim is resized. +A('VimResized', { + command = 'wincmd =', +}) + + +-- Calculate 'numberwidth' to fit file size. +-- Note: extra 2 is the room of left and right spaces. +A({'BufEnter', 'WinEnter', 'BufWinEnter'}, { + callback = function() + vim.wo.numberwidth = #tostring(F.line('$')) + 2 + end, +}) + + +-- Jump to the last cursor position when you open a file. +A('BufRead', { + callback = function() + if 0 < F.line("'\"") and F.line("'\"") <= F.line('$') and + not O.filetype:match('commit') and not O.filetype:match('rebase') + then + vim.cmd('normal g`"') + end + end, +}) + + +-- Lua version of https://github.com/mopp/autodirmake.vim +-- License: NYSL +A('BufWritePre', { + callback = function() + local dir = F.expand('<afile>:p:h') + if F.isdirectory(dir) ~= 0 then + return + end + vimrc.echo(('"%s" does not exist. Create? [y/N] '):format(dir), 'Question') + local answer = vimrc.getchar() + if answer ~= 'y' and answer ~= 'Y' then + return + end + F.mkdir(dir, 'p') + end, +}) + + +vimrc.register_filetype_autocmds_for_indentation() diff --git a/.config/nvim/lua/init/04-mappings.lua b/.config/nvim/lua/init/04-mappings.lua new file mode 100644 index 0000000..f7f8a41 --- /dev/null +++ b/.config/nvim/lua/init/04-mappings.lua @@ -0,0 +1,563 @@ +local F = vim.fn +local G = vim.g +local O = vim.o +local K = vim.keymap + +local vimrc = require('vimrc') + + +-- Note: |:noremap| defines mappings in |Normal|, |Visual|, |Operator-Pending| +-- and |Select| mode. Because I don't use |Select| mode, I use |:noremap| +-- instead of |:nnoremap|, |:xnoremap| and |:onoremap| for simplicity. + + +-- Searching {{{1 + +-- Fix direction of n and N. +K.set('', 'n', "v:searchforward ? 'n' : 'N'", { expr=true }) +K.set('', 'N', "v:searchforward ? 'N' : 'n'", { expr=true }) + +K.set('', 'gn', "v:searchforward ? 'gn' : 'gN'", { expr=true }) +K.set('', 'gN', "v:searchforward ? 'gN' : 'gn'", { expr=true }) + +K.set({'n', 'x'}, '&', '<Cmd>%&&<CR>') + + +-- Registers and macros. {{{1 + +-- Access an resister in the same way in Insert and Commandline mode. +K.set({'n', 'x'}, '<C-r>', '"') + +F.setreg('j', 'j.') +F.setreg('k', 'k.') +F.setreg('n', 'n.') +F.setreg('m', 'N.') +K.set('n', '@N', '@m') + +-- Repeat the last executed macro as many times as possible. +-- a => all +K.set('n', '@a', '9999@@') + +-- Execute the last executed macro again. +K.set('n', '`', '@@') + + +-- Emacs like key mappings in Insert and CommandLine mode. {{{1 + +K.set('i', '<C-d>', '<Del>') + +-- Go elsewhere without dividing the undo history. +K.set('i', '<C-b>', '<C-g>U<Left>') +K.set('i', '<C-f>', '<C-g>U<Right>') + +-- Delete something deviding the undo history. +K.set('i', '<C-u>', '<C-g>u<C-u>') +K.set('i', '<C-w>', '<C-g>u<C-w>') + +K.set('c', '<C-a>', '<Home>') +K.set('c', '<C-e>', '<End>') +K.set('c', '<C-f>', '<Right>') +K.set('c', '<C-b>', '<Left>') +K.set('c', '<C-n>', '<Down>') +K.set('c', '<C-p>', '<Up>') +K.set('c', '<C-d>', '<Del>') + +K.set({'c', 'i'}, '<Left>', '<Nop>') +K.set({'c', 'i'}, '<Right>', '<Nop>') + + + +K.set('n', 'gA', function() + local line = F.getline('.') + if vim.endswith(line, ';;') then -- for OCaml + return 'A<C-g>U<Left><C-g>U<Left>' + elseif vim.regex('[,;)]$'):match_str(line) then + return 'A<C-g>U<Left>' + else + return 'A' + end +end, { expr=true, replace_keycodes=true }) + + + +-- QuickFix or location list. {{{1 + +K.set('n', 'bb', '<Cmd>cc<CR>') + +K.set('n', 'bn', ':<C-u><C-r>=v:count1<CR>cnext<CR>', { silent = true }) +K.set('n', 'bp', ':<C-u><C-r>=v:count1<CR>cprevious<CR>', { silent = true }) + +K.set('n', 'bf', '<Cmd>cfirst<CR>') +K.set('n', 'bl', '<Cmd>clast<CR>') + +K.set('n', 'bS', '<Cmd>colder<CR>') +K.set('n', 'bs', '<Cmd>cnewer<CR>') + + + +-- Operators {{{1 + +-- Throw deleted text into the black hole register ("_). +K.set({'n', 'x'}, 'c', '"_c') +K.set({'n', 'x'}, 'C', '"_C') + + +K.set('', 'g=', '=') + + +K.set('', 'ml', 'gu') +K.set('', 'mu', 'gU') + +K.set('', 'gu', '<Nop>') +K.set('', 'gU', '<Nop>') +K.set('x', 'u', '<Nop>') +K.set('x', 'U', '<Nop>') + + +K.set('x', 'x', '"_x') + + +K.set('n', 'Y', 'y$') +-- In Blockwise-Visual mode, select text linewise. +-- By default, select text characterwise, neither blockwise nor linewise. +K.set('x', 'Y', "mode() ==# 'V' ? 'y' : 'Vy'", { expr=true }) + + + +-- Swap the keys entering Replace mode and Visual-Replace mode. +K.set('n', 'R', 'gR') +K.set('n', 'gR', 'R') +K.set('n', 'r', 'gr') +K.set('n', 'gr', 'r') + + +K.set('n', 'U', '<C-r>') + + + + +-- Motions {{{1 + +K.set('', 'H', '^') +K.set('', 'L', '$') +K.set('', 'M', '%') + +K.set('', 'gw', 'b') +K.set('', 'gW', 'B') + +K.set('', 'k', 'gk') +K.set('', 'j', 'gj') +K.set('', 'gk', 'k') +K.set('', 'gj', 'j') + +K.set('n', 'gff', 'gF') + + + +-- Tabpages and windows. {{{1 + +local function move_current_window_to_tabpage() + if F.winnr('$') == 1 then + -- Leave the current window and open it in a new tabpage. + -- Because :wincmd T fails when the current tabpage has only one window. + vim.cmd('tab split') + else + -- Close the current window and re-open it in a new tabpage. + vim.cmd('wincmd T') + end +end + + +local function choose_window_interactively() + local indicators = { + 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + } + + -- List normal windows up to 20. + local wins = {} + for winnr = 1, F.winnr('$') do + if winnr ~= F.winnr() and F.win_gettype(winnr) == '' then + wins[#wins+1] = F.win_getid(winnr) + end + end + if #indicators < #wins then + for i = #indicators+1, #wins do + wins[i] = nil + end + end + + -- Handle special cases. + if #wins == 0 then + return + end + if #wins == 1 then + if wins[1] == F.win_getid() then + F.win_gotoid(wins[2]) + else + F.win_gotoid(wins[1]) + end + return + end + + -- Show popups. + local popups = {} + for i = 1, #wins do + local winid = wins[i] + local indicator = indicators[i] + local buf_id = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf_id, 0, -1, true, { ' ' .. indicator .. ' ' }) + local popup = vim.api.nvim_open_win( + buf_id, + false, + { + relative = 'win', + win = winid, + row = (F.winheight(winid) - 5) / 2, + col = (F.winwidth(winid) - 9) / 2, + width = 5, + height = 1, + focusable = false, + style = 'minimal', + border = 'solid', + noautocmd = true, + }) + popups[#popups+1] = { + winid = popup, + indicator = indicator, + target_winid = winid, + } + end + + -- Prompt + local result = vimrc.getchar_with_prompt('Select window: ') + + -- Jump + local jump_target = -1 + for i, popup in ipairs(popups) do + if string.upper(result) == popup.indicator then + jump_target = popup.target_winid + end + end + if jump_target ~= -1 then + F.win_gotoid(jump_target) + end + + -- Close popups + for i, popup in ipairs(popups) do + vim.api.nvim_win_close(popup.winid, true) + end +end + + +K.set('n', 'tt', '<Cmd>tabnew<CR>') +K.set('n', 'tT', move_current_window_to_tabpage) + +K.set('n', 'tn', ":<C-u><C-r>=(tabpagenr() + v:count1 - 1) % tabpagenr('$') + 1<CR>tabnext<CR>", { silent=true }) +K.set('n', 'tp', ":<C-u><C-r>=(tabpagenr('$') * 10 + tabpagenr() - v:count1 - 1) % tabpagenr('$') + 1<CR>tabnext<CR>", { silent=true }) + +K.set('n', 'tN', '<Cmd>tabmove +<CR>') +K.set('n', 'tP', '<Cmd>tabmove -<CR>') + +K.set('n', 'tsh', '<Cmd>leftabove vsplit<CR>') +K.set('n', 'tsj', '<Cmd>rightbelow split<CR>') +K.set('n', 'tsk', '<Cmd>leftabove split<CR>') +K.set('n', 'tsl', '<Cmd>rightbelow vsplit<CR>') + +K.set('n', 'tsH', '<Cmd>topleft vsplit<CR>') +K.set('n', 'tsJ', '<Cmd>botright split<CR>') +K.set('n', 'tsK', '<Cmd>topleft split<CR>') +K.set('n', 'tsL', '<Cmd>botright vsplit<CR>') + +K.set('n', 'twh', '<Cmd>leftabove vnew<CR>') +K.set('n', 'twj', '<Cmd>rightbelow new<CR>') +K.set('n', 'twk', '<Cmd>leftabove new<CR>') +K.set('n', 'twl', '<Cmd>rightbelow vnew<CR>') + +K.set('n', 'twH', '<Cmd>topleft vnew<CR>') +K.set('n', 'twJ', '<Cmd>botright new<CR>') +K.set('n', 'twK', '<Cmd>topleft new<CR>') +K.set('n', 'twL', '<Cmd>botright vnew<CR>') + +K.set('n', 'th', '<C-w>h') +K.set('n', 'tj', '<C-w>j') +K.set('n', 'tk', '<C-w>k') +K.set('n', 'tl', '<C-w>l') + +K.set('n', 'tH', '<C-w>H') +K.set('n', 'tJ', '<C-w>J') +K.set('n', 'tK', '<C-w>K') +K.set('n', 'tL', '<C-w>L') + +K.set('n', 'tx', '<C-w>x') + +-- r => manual resize. +-- R => automatic resize. +K.set('n', 'tRH', '<C-w>_') +K.set('n', 'tRW', '<C-w><Bar>') +K.set('n', 'tRR', '<C-w>_<C-w><Bar>') + +K.set('n', 't=', '<C-w>=') + +K.set('n', 'tq', '<Cmd>bdelete<CR>') + +K.set('n', 'tc', '<C-w>c') + +K.set('n', 'to', '<C-w>o') +K.set('n', 'tO', '<Cmd>tabonly<CR>') + +K.set('n', 'tg', choose_window_interactively) + + + +local function smart_open(command) + local modifiers + if F.winwidth(F.winnr()) < 150 then + modifiers = 'topleft' + else + modifiers = 'vertical botright' + end + + vim.cmd(([[ + try + %s %s + let g:__ok = v:true + catch + echohl Error + echo v:exception + echohl None + let g:__ok = v:false + endtry + ]]):format(modifiers, command)) + if not G.__ok then + return + end + + if O.filetype == 'help' then + if vim.bo.textwidth > 0 then + vim.cmd(('vertical resize %d'):format(vim.bo.textwidth)) + end + -- Move the cursor to the beginning of the line as help tags are often + -- right-justfied. + F.cursor( + 0 --[[ stay in the current line ]], + 1 --[[ move to the beginning of the line ]]) + end +end + +vim.api.nvim_create_user_command( + 'SmartOpen', + function(opts) smart_open(opts.args) end, + { + desc = 'Smartly open a new buffer', + nargs = '+', + complete = 'command', + } +) + + + + +-- Toggle options {{{1 + +K.set('n', 'T', '<Nop>') + +K.set('n', 'Ta', '<Cmd>AutosaveToggle<CR>') +K.set('n', 'Tb', ':<C-u>if &background == "dark" <Bar>set background=light <Bar>else <Bar>set background=dark <Bar>endif<CR>', { silent=true }) +K.set('n', 'Tc', ':<C-u>set cursorcolumn! <Bar>set cursorline!<CR>', { silent=true }) +K.set('n', 'Td', ':<C-u>if &diff <Bar>diffoff <Bar>else <Bar>diffthis <Bar>endif<CR>', { silent=true }) +K.set('n', 'Te', '<Cmd>set expandtab!<CR>') +K.set('n', 'Th', '<Cmd>set hlsearch!<CR>') +K.set('n', 'Tn', '<Cmd>set number!<CR>') +K.set('n', 'Ts', '<Cmd>set spell!<CR>') +K.set('n', 'T8', ':<C-u>if &textwidth ==# 80 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=80 <Bar>endif<CR>', { silent=true }) +K.set('n', 'T0', ':<C-u>if &textwidth ==# 100 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=100 <Bar>endif<CR>', { silent=true }) +K.set('n', 'T2', ':<C-u>if &textwidth ==# 120 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=120 <Bar>endif<CR>', { silent=true }) +K.set('n', 'Tw', '<Cmd>set wrap!<CR>') + +K.set('n', 'TA', 'Ta', { remap=true }) +K.set('n', 'TB', 'Tb', { remap=true }) +K.set('n', 'TC', 'Tc', { remap=true }) +K.set('n', 'TD', 'Td', { remap=true }) +K.set('n', 'TE', 'Te', { remap=true }) +K.set('n', 'TH', 'Th', { remap=true }) +K.set('n', 'TN', 'Tn', { remap=true }) +K.set('n', 'TS', 'Ts', { remap=true }) +K.set('n', 'TW', 'Tw', { remap=true }) + + + +-- Increment/decrement numbers {{{1 + +-- nnoremap + <C-a> +-- nnoremap - <C-x> +-- xnoremap + <C-a> +-- xnoremap - <C-x> +-- xnoremap g+ g<C-a> +-- xnoremap g- g<C-x> + + + +-- Open *scratch* buffer {{{1 + +local EXTENSION_MAPPING = { + bash = 'sh', + haskell = 'hs', + javascript = 'js', + markdown = 'md', + python = 'py', + ruby = 'rb', + rust = 'rs', + typescript = 'ts', + zsh = 'sh', +} + +local function make_scratch_buffer_name(ft) + local now = F.localtime() + if ft == '' then + ft = 'txt' + end + local ext = EXTENSION_MAPPING[ft] or ft + return my_env.scratch_dir .. '/' .. F.strftime('%Y-%m', now), F.strftime('%Y-%m-%d-%H%M%S'), ext +end + +local function open_scratch() + local ok, ft = pcall(function() return vimrc.input('filetype: ') end) + if not ok then + vimrc.echo('Canceled', 'ErrorMsg') + return + end + ft = vim.trim(ft) + local dir, fname, ext = make_scratch_buffer_name(ft) + if F.isdirectory(dir) == 0 then + F.mkdir(dir, 'p') + end + vim.cmd(('SmartTabEdit %s/%s.%s'):format(dir, fname, ext)) + if vim.bo.filetype ~= ft then + vim.cmd('setlocal filetype=' .. ft) + end + vim.b._scratch_ = true + if vim.fn.exists(':AutosaveEnable') == 2 then + vim.cmd('AutosaveEnable') + end +end + +vim.api.nvim_create_user_command( + 'Scratch', + function() open_scratch() end, + { + desc = 'Open a *scratch* buffer', + } +) + +K.set('n', '<Space>s', '<Cmd>Scratch<CR>') + + + +-- Disable unuseful or dangerous mappings. {{{1 + +-- Disable Select mode. +K.set('n', 'gh', '<Nop>') +K.set('n', 'gH', '<Nop>') +K.set('n', 'g<C-h>', '<Nop>') + +-- Disable Ex mode. +K.set('n', 'Q', '<Nop>') +K.set('n', 'gQ', '<Nop>') + +K.set('n', 'ZZ', '<Nop>') +K.set('n', 'ZQ', '<Nop>') + + +-- Help {{{1 + +-- Search help. +K.set('n', '<C-h>', ':<C-u>SmartOpen help<Space>') + + + +-- For writing Vim script. {{{1 + +K.set('n', 'XV', '<Cmd>SmartTabEdit $MYVIMRC<CR>') + +-- See |numbered-function|. +K.set('n', 'XF', ':<C-u>function {<C-r>=v:count<CR>}<CR>', { silent=true }) + +K.set('n', 'XM', '<Cmd>messages<CR>') + + + + + + + +-- Abbreviations {{{1 + +vimrc.iabbrev('TOOD', 'TODO') +vimrc.iabbrev('retrun', 'return') +vimrc.iabbrev('reutrn', 'return') +vimrc.iabbrev('tihs', 'this') + + +vimrc.cabbrev('S', '%s') + + + +-- Misc. {{{1 + +K.set('o', 'gv', ':<C-u>normal! gv<CR>', { silent=true }) + +-- Swap : and ;. +K.set('n', ';', ':') +K.set('n', ':', ';') +K.set('x', ';', ':') +K.set('x', ':', ';') +K.set('n', '@;', '@:') +K.set('x', '@;', '@:') +K.set('!', '<C-r>;', '<C-r>:') + + +-- Since <ESC> may be mapped to something else somewhere, it should be :map, not +-- :noremap. +K.set('!', 'jk', '<ESC>', { remap=true }) + + + +K.set('n', '<C-c>', ':<C-u>nohlsearch<CR>', { silent=true }) + + +-- Lua function cannot be set to 'operatorfunc' for now. +vim.cmd([[ + function! Vimrc_insert_black_line_below(type = '') abort + if a:type ==# '' + set operatorfunc=Vimrc_insert_black_line_below + return 'g@ ' + else + for i in range(v:count1) + call append(line('.'), '') + endfor + endif + endfunction + function! Vimrc_insert_black_line_above(type = '') abort + if a:type ==# '' + set operatorfunc=Vimrc_insert_black_line_above + return 'g@ ' + else + for i in range(v:count1) + call append(line('.') - 1, '') + endfor + endif + endfunction +]]) +K.set('n', 'go', F.Vimrc_insert_black_line_below, { expr = true }) +K.set('n', 'gO', F.Vimrc_insert_black_line_above, { expr = true }) + + +K.set('n', '<Space>w', '<Cmd>update<CR>') + +K.set('n', 'Z', '<Cmd>wqall<CR>', { nowait = true }) + + +-- `s` is used as a prefix key of plugin sandwich and hop. +K.set('n', 's', '<Nop>') +K.set('x', 's', '<Nop>') diff --git a/.config/nvim/lua/init/05-appearance.lua b/.config/nvim/lua/init/05-appearance.lua new file mode 100644 index 0000000..29ba657 --- /dev/null +++ b/.config/nvim/lua/init/05-appearance.lua @@ -0,0 +1,227 @@ +local F = vim.fn +local G = vim.g +local O = vim.o +local uniquify = require('uniquify') +local vimrc = require('vimrc') + + +-- Color scheme {{{1 + +if not pcall(function() vim.cmd('colorscheme ocean') end) then + -- Load "desert", one of the built-in colorschemes, instead of mine + -- when nvim failed to load it. + vim.cmd('colorscheme desert') +end + + +-- Statusline {{{1 + +O.statusline = '%!v:lua.vimrc.statusline.build()' + +vimrc.statusline = {} + +function vimrc.statusline.build() + local winid = G.statusline_winid + local bufnr = F.winbufnr(winid) + local is_active = winid == F.win_getid() + local left + if is_active then + local mode, mode_hl = vimrc.statusline.mode() + left = string.format('%%#statusLineMode%s# %s %%#statusLine#', mode_hl, mode) + else + left = '' + end + local ro = vimrc.statusline.readonly(bufnr) + local fname = vimrc.statusline.filename(bufnr) + local mod = vimrc.statusline.modified(bufnr) + local extra_info = vimrc.statusline.extra_info(bufnr, winid) + local linenum = vimrc.statusline.linenum(winid) + local fenc = vimrc.statusline.fenc(bufnr) + local eol = vimrc.statusline.eol(bufnr) + local ff = vimrc.statusline.ff(bufnr) + local ft = vimrc.statusline.filetype(bufnr) + return string.format( + '%s %s%s%s %%= %s%s %s%s%s %s ', + left, + ro and ro .. ' ' or '', + fname, + mod and ' ' .. mod or '', + extra_info == '' and '' or extra_info .. ' ', + linenum, + fenc, + eol, + ff, + ft) +end + +function vimrc.statusline.mode() + local mode_map = { + n = { 'N', 'Normal' }, + no = { 'O', 'Operator' }, + nov = { 'Oc', 'Operator' }, + noV = { 'Ol', 'Operator' }, + [vimrc.term('no<C-v>')] = { 'Ob', 'Operator' }, + niI = { 'In', 'Insert' }, + niR = { 'Rn', 'Replace' }, + niV = { 'Rn', 'Replace' }, + v = { 'V', 'Visual' }, + V = { 'Vl', 'Visual' }, + [vimrc.term('<C-v>')] = { 'Vb', 'Visual' }, + s = { 'S', 'Visual' }, + S = { 'Sl', 'Visual' }, + [vimrc.term('<C-s>')] = { 'Sb', 'Visual' }, + i = { 'I', 'Insert' }, + ic = { 'I?', 'Insert' }, + ix = { 'I?', 'Insert' }, + R = { 'R', 'Replace' }, + Rc = { 'R?', 'Replace' }, + Rv = { 'R', 'Replace' }, + Rx = { 'R?', 'Replace' }, + c = { 'C', 'Command' }, + cv = { 'C', 'Command' }, + ce = { 'C', 'Command' }, + r = { '-', 'Other' }, + rm = { '-', 'Other' }, + ['r?'] = { '-', 'Other' }, + ['!'] = { '-', 'Other' }, + t = { 'T', 'Terminal' }, + } + local vim_mode_and_hl = mode_map[F.mode(true)] or { '-', 'Other' } + local vim_mode = vim_mode_and_hl[1] + local hl = vim_mode_and_hl[2] + + -- Calling `eskk#statusline()` makes Vim autoload eskk. If you call it + -- without checking `g:loaded_autoload_eskk`, eskk is loaded on an early + -- stage of the initialization (probably the first rendering of status line), + -- which slows down Vim startup. Loading eskk can be delayed by checking both + -- of `g:loaded_eskk` and `g:loaded_autoload_eskk`. + local skk_mode + if G.loaded_eskk and G.loaded_autoload_eskk then + skk_mode = F['eskk#statusline'](' (%s)', '') + else + skk_mode = '' + end + + return vim_mode .. skk_mode, hl +end + +function vimrc.statusline.readonly(bufnr) + local ro = F.getbufvar(bufnr, '&readonly') + if ro == 1 then + return '[RO]' + else + return nil + end +end + +function vimrc.statusline.filename(bufnr) + if F.bufname(bufnr) == '' then + return '[No Name]' + end + if vim.b[bufnr]._scratch_ then + return '*scratch*' + end + + local this_path = F.expand(('#%s:p'):format(bufnr)) + local other_paths = {} + for b = 1, F.bufnr('$') do + if F.bufexists(b) and b ~= bufnr then + other_paths[#other_paths+1] = F.bufname(b) + end + end + + return uniquify.uniquify(this_path, other_paths) +end + +function vimrc.statusline.modified(bufnr) + local mod = F.getbufvar(bufnr, '&modified') + local ma = F.getbufvar(bufnr, '&modifiable') + if mod == 1 then + return '[+]' + elseif ma == 0 then + return '[-]' + else + return nil + end +end + +function vimrc.statusline.extra_info(bufnr, winid) + local autosave = F.getbufvar(bufnr, 'autosave_timer_id', -1) ~= -1 + local spell = F.getwinvar(winid, '&spell') == 1 + return (autosave and '(A)' or '') .. (spell and '(S)' or '') +end + +function vimrc.statusline.linenum(winid) + return F.line('.', winid) .. '/' .. F.line('$', winid) +end + +function vimrc.statusline.fenc(bufnr) + local fenc = F.getbufvar(bufnr, '&fileencoding') + local bom = F.getbufvar(bufnr, '&bomb') -- BOMB!! + + if fenc == '' then + local fencs = F.split(O.fileencodings, ',') + fenc = fencs[1] or O.encoding + end + if fenc == 'utf-8' then + return bom == 1 and 'U8[BOM]' or 'U8' + elseif fenc == 'utf-16' then + return 'U16[BE]' + elseif fenc == 'utf-16le' then + return 'U16[LE]' + elseif fenc == 'ucs-4' then + return 'U32[BE]' + elseif fenc == 'ucs-4le' then + return 'U32[LE]' + else + return fenc:upper() + end +end + +function vimrc.statusline.eol(bufnr) + local eol = F.getbufvar(bufnr, '&endofline') + return eol == 0 and '[noeol]' or '' +end + +function vimrc.statusline.ff(bufnr) + local ff = F.getbufvar(bufnr, '&fileformat') + if ff == 'unix' then + return '' + elseif ff == 'dos' then + return ' (CRLF)' + elseif ff == 'mac' then + return ' (CR)' + else + return ' (Unknown)' + end +end + +function vimrc.statusline.filetype(bufnr) + local ft = F.getbufvar(bufnr, '&filetype') + if ft == '' then + return '[None]' + else + return ft + end +end + + +-- Tabline {{{1 + +O.tabline = '%!v:lua.vimrc.tabline.build()' + +vimrc.tabline = {} + +function vimrc.tabline.build() + local tal = '' + for tabnr = 1, F.tabpagenr('$') do + local is_active = tabnr == F.tabpagenr() + local buflist = F.tabpagebuflist(tabnr) + local bufnr = buflist[F.tabpagewinnr(tabnr)] + tal = tal .. string.format( + '%%#%s# %s ', + is_active and 'TabLineSel' or 'TabLine', + vimrc.statusline.filename(bufnr)) + end + return tal .. '%#TabLineFill#' +end diff --git a/.config/nvim/lua/init/06-plugins.lua b/.config/nvim/lua/init/06-plugins.lua new file mode 100644 index 0000000..f8af4a5 --- /dev/null +++ b/.config/nvim/lua/init/06-plugins.lua @@ -0,0 +1,30 @@ +--- Disable standard plugins. {{{1 + +vim.g.loaded_gzip = 1 +vim.g.loaded_matchparen = 1 +vim.g.loaded_netrw = 1 +vim.g.loaded_netrwPlugin = 1 +vim.g.loaded_spellfile_plugin = 1 +vim.g.loaded_tarPlugin = 1 +vim.g.loaded_zipPlugin = 1 + + +--- Load and configure third-party plugins. {{{1 +vim.api.nvim_create_user_command( + 'PackerSync', + function() require('vimrc.plugins').sync() end, + { + desc = '[packer.nvim] Synchronize plugins', + } +) +vimrc.autocmd('BufWritePost', { + pattern = {'plugins.lua'}, + callback = function() + vim.cmd('source <afile>') + vimrc.autocmd('User', { + pattern = 'PackerCompileDone', + command = 'echo "[packer] Finished compiling lazy-loaders!"' + }) + require('vimrc.plugins').compile() + end, +}) |
