diff options
| -rw-r--r-- | .config/nvim/init.lua | 1139 | ||||
| -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 |
8 files changed, 1096 insertions, 1122 deletions
diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua index 4905742..487befc 100644 --- a/.config/nvim/init.lua +++ b/.config/nvim/init.lua @@ -1,1122 +1,17 @@ ---[========================================================================[-- --- -- --- _ __ _ _ _ _ -- --- _ ____ _(_)_ __ ___ / / (_)_ __ (_) |_ | |_ _ __ _ -- --- | '_ \ \ / / | '_ ` _ \ / / | | '_ \| | __| | | | | |/ _` | -- --- | | | \ V /| | | | | | | / / | | | | | | |_ _| | |_| | (_| | -- --- |_| |_|\_/ |_|_| |_| |_| /_/ |_|_| |_|_|\__(_)_|\__,_|\__,_| -- --- -- ---]========================================================================]-- - - - --- Global settings {{{1 - --- Aliases {{{2 - -local F = vim.fn -local G = vim.g -local O = vim.o -local OPT = vim.opt - -local uniquify = require('uniquify') -local vimrc = require('vimrc') -_G.vimrc = vimrc - - - --- Environment {{{2 - -local my_env = require('vimrc.my_env') -my_env.mkdir() - - - --- The augroup used in .vimrc {{{2 - -vimrc.create_augroup_for_vimrc() - - - --- Language {{{2 - --- Disable L10N. -vim.cmd('language messages C') -vim.cmd('language time C') - - - - --- Options {{{1 - --- Moving around, searching and patterns {{{2 - -O.wrapscan = false -O.ignorecase = true -O.smartcase = true - - --- Displaying text {{{2 - -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 {{{2 - -O.background = 'dark' -O.synmaxcol = 500 -O.hlsearch = true -O.termguicolors = true -O.colorcolumn = '+1' - - --- Multiple windows {{{2 - -O.winminheight = 0 -O.hidden = true -O.switchbuf = 'usetab' - - --- Multiple tabpages {{{2 - -O.showtabline = 2 - - --- Terminal {{{2 - -O.title = false - - --- Using the mouse {{{2 - -O.mouse = '' - - --- Messages and info {{{2 - -OPT.shortmess:append('asIc') -O.showmode = false -O.report = 999 -O.confirm = true - - --- Selecting text {{{2 - -O.clipboard = 'unnamed' - - --- Editing text {{{2 - -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 {{{2 --- 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 {{{2 - -O.foldlevelstart = 0 -OPT.foldopen:append('insert') -O.foldmethod = 'marker' - - --- Diff mode {{{2 - -OPT.diffopt:append('vertical') -OPT.diffopt:append('foldcolumn:3') - - --- Mapping {{{2 - -O.maxmapdepth = 10 -O.timeout = false - - --- Reading and writing files {{{2 - -O.fixendofline = false -O.fileformats = 'unix,dos' -O.backup = true -O.backupdir = my_env.backup_dir -O.autowrite = true - - --- Command line editing {{{2 - -OPT.wildignore:append('*.o') -OPT.wildignore:append('*.obj') -OPT.wildignore:append('*.lib') -O.wildignorecase = true - - --- Executing external commands {{{2 - -O.shell = 'zsh' -O.keywordprg = '' - - --- Encoding {{{2 - -O.fileencodings = 'utf-8,cp932,euc-jp' - - --- Misc. {{{2 - - --- Autocommands {{{1 - --- Auto-resize windows when Vim is resized. -vimrc.autocmd('VimResized', { - command = 'wincmd =', -}) - - --- Calculate 'numberwidth' to fit file size. --- Note: extra 2 is the room of left and right spaces. -vimrc.autocmd({'BufEnter', 'WinEnter', 'BufWinEnter'}, { - callback = function() - vim.wo.numberwidth = #tostring(F.line('$')) + 2 - end, -}) - - --- Jump to the last cursor position when you open a file. -vimrc.autocmd('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 -vimrc.autocmd('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() - - - --- Mappings {{{1 - --- 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 {{{2 - --- Fix direction of n and N. -vim.keymap.set('', 'n', "v:searchforward ? 'n' : 'N'", { expr=true }) -vim.keymap.set('', 'N', "v:searchforward ? 'N' : 'n'", { expr=true }) - -vim.keymap.set('', 'gn', "v:searchforward ? 'gn' : 'gN'", { expr=true }) -vim.keymap.set('', 'gN', "v:searchforward ? 'gN' : 'gn'", { expr=true }) - -vim.keymap.set({'n', 'x'}, '&', '<Cmd>%&&<CR>') - - --- Registers and macros. {{{2 - --- Access an resister in the same way in Insert and Commandline mode. -vim.keymap.set({'n', 'x'}, '<C-r>', '"') - -F.setreg('j', 'j.') -F.setreg('k', 'k.') -F.setreg('n', 'n.') -F.setreg('m', 'N.') -vim.keymap.set('n', '@N', '@m') - --- Repeat the last executed macro as many times as possible. --- a => all -vim.keymap.set('n', '@a', '9999@@') - --- Execute the last executed macro again. -vim.keymap.set('n', '`', '@@') - - --- Emacs like key mappings in Insert and CommandLine mode. {{{2 - -vim.keymap.set('i', '<C-d>', '<Del>') - --- Go elsewhere without dividing the undo history. -vim.keymap.set('i', '<C-b>', '<C-g>U<Left>') -vim.keymap.set('i', '<C-f>', '<C-g>U<Right>') - --- Delete something deviding the undo history. -vim.keymap.set('i', '<C-u>', '<C-g>u<C-u>') -vim.keymap.set('i', '<C-w>', '<C-g>u<C-w>') - -vim.keymap.set('c', '<C-a>', '<Home>') -vim.keymap.set('c', '<C-e>', '<End>') -vim.keymap.set('c', '<C-f>', '<Right>') -vim.keymap.set('c', '<C-b>', '<Left>') -vim.keymap.set('c', '<C-n>', '<Down>') -vim.keymap.set('c', '<C-p>', '<Up>') -vim.keymap.set('c', '<C-d>', '<Del>') - -vim.keymap.set({'c', 'i'}, '<Left>', '<Nop>') -vim.keymap.set({'c', 'i'}, '<Right>', '<Nop>') - - - -vim.keymap.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. {{{2 - -vim.keymap.set('n', 'bb', '<Cmd>cc<CR>') - -vim.keymap.set('n', 'bn', ':<C-u><C-r>=v:count1<CR>cnext<CR>', { silent = true }) -vim.keymap.set('n', 'bp', ':<C-u><C-r>=v:count1<CR>cprevious<CR>', { silent = true }) - -vim.keymap.set('n', 'bf', '<Cmd>cfirst<CR>') -vim.keymap.set('n', 'bl', '<Cmd>clast<CR>') - -vim.keymap.set('n', 'bS', '<Cmd>colder<CR>') -vim.keymap.set('n', 'bs', '<Cmd>cnewer<CR>') - - - --- Operators {{{2 - --- Throw deleted text into the black hole register ("_). -vim.keymap.set({'n', 'x'}, 'c', '"_c') -vim.keymap.set({'n', 'x'}, 'C', '"_C') - - -vim.keymap.set('', 'g=', '=') - - -vim.keymap.set('', 'ml', 'gu') -vim.keymap.set('', 'mu', 'gU') - -vim.keymap.set('', 'gu', '<Nop>') -vim.keymap.set('', 'gU', '<Nop>') -vim.keymap.set('x', 'u', '<Nop>') -vim.keymap.set('x', 'U', '<Nop>') - - -vim.keymap.set('x', 'x', '"_x') - - -vim.keymap.set('n', 'Y', 'y$') --- In Blockwise-Visual mode, select text linewise. --- By default, select text characterwise, neither blockwise nor linewise. -vim.keymap.set('x', 'Y', "mode() ==# 'V' ? 'y' : 'Vy'", { expr=true }) - - - --- Swap the keys entering Replace mode and Visual-Replace mode. -vim.keymap.set('n', 'R', 'gR') -vim.keymap.set('n', 'gR', 'R') -vim.keymap.set('n', 'r', 'gr') -vim.keymap.set('n', 'gr', 'r') - - -vim.keymap.set('n', 'U', '<C-r>') - - - - --- Motions {{{2 - -vim.keymap.set('', 'H', '^') -vim.keymap.set('', 'L', '$') -vim.keymap.set('', 'M', '%') - -vim.keymap.set('', 'gw', 'b') -vim.keymap.set('', 'gW', 'B') - -vim.keymap.set('', 'k', 'gk') -vim.keymap.set('', 'j', 'gj') -vim.keymap.set('', 'gk', 'k') -vim.keymap.set('', 'gj', 'j') - -vim.keymap.set('n', 'gff', 'gF') - - - --- Tabpages and windows. {{{2 - -vimrc.fn = {} - -function vimrc.fn.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 - - -function vimrc.fn.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 - - -vim.keymap.set('n', 'tt', '<Cmd>tabnew<CR>') -vim.keymap.set('n', 'tT', vimrc.fn.move_current_window_to_tabpage) - -vim.keymap.set('n', 'tn', ":<C-u><C-r>=(tabpagenr() + v:count1 - 1) % tabpagenr('$') + 1<CR>tabnext<CR>", { silent=true }) -vim.keymap.set('n', 'tp', ":<C-u><C-r>=(tabpagenr('$') * 10 + tabpagenr() - v:count1 - 1) % tabpagenr('$') + 1<CR>tabnext<CR>", { silent=true }) - -vim.keymap.set('n', 'tN', '<Cmd>tabmove +<CR>') -vim.keymap.set('n', 'tP', '<Cmd>tabmove -<CR>') - -vim.keymap.set('n', 'tsh', '<Cmd>leftabove vsplit<CR>') -vim.keymap.set('n', 'tsj', '<Cmd>rightbelow split<CR>') -vim.keymap.set('n', 'tsk', '<Cmd>leftabove split<CR>') -vim.keymap.set('n', 'tsl', '<Cmd>rightbelow vsplit<CR>') - -vim.keymap.set('n', 'tsH', '<Cmd>topleft vsplit<CR>') -vim.keymap.set('n', 'tsJ', '<Cmd>botright split<CR>') -vim.keymap.set('n', 'tsK', '<Cmd>topleft split<CR>') -vim.keymap.set('n', 'tsL', '<Cmd>botright vsplit<CR>') - -vim.keymap.set('n', 'twh', '<Cmd>leftabove vnew<CR>') -vim.keymap.set('n', 'twj', '<Cmd>rightbelow new<CR>') -vim.keymap.set('n', 'twk', '<Cmd>leftabove new<CR>') -vim.keymap.set('n', 'twl', '<Cmd>rightbelow vnew<CR>') - -vim.keymap.set('n', 'twH', '<Cmd>topleft vnew<CR>') -vim.keymap.set('n', 'twJ', '<Cmd>botright new<CR>') -vim.keymap.set('n', 'twK', '<Cmd>topleft new<CR>') -vim.keymap.set('n', 'twL', '<Cmd>botright vnew<CR>') - -vim.keymap.set('n', 'th', '<C-w>h') -vim.keymap.set('n', 'tj', '<C-w>j') -vim.keymap.set('n', 'tk', '<C-w>k') -vim.keymap.set('n', 'tl', '<C-w>l') - -vim.keymap.set('n', 'tH', '<C-w>H') -vim.keymap.set('n', 'tJ', '<C-w>J') -vim.keymap.set('n', 'tK', '<C-w>K') -vim.keymap.set('n', 'tL', '<C-w>L') - -vim.keymap.set('n', 'tx', '<C-w>x') - --- r => manual resize. --- R => automatic resize. -vim.keymap.set('n', 'tRH', '<C-w>_') -vim.keymap.set('n', 'tRW', '<C-w><Bar>') -vim.keymap.set('n', 'tRR', '<C-w>_<C-w><Bar>') - -vim.keymap.set('n', 't=', '<C-w>=') - -vim.keymap.set('n', 'tq', '<Cmd>bdelete<CR>') - -vim.keymap.set('n', 'tc', '<C-w>c') - -vim.keymap.set('n', 'to', '<C-w>o') -vim.keymap.set('n', 'tO', '<Cmd>tabonly<CR>') - -vim.keymap.set('n', 'tg', vimrc.fn.choose_window_interactively) - - - -function vimrc.fn.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) vimrc.fn.smart_open(opts.args) end, - { - desc = 'Smartly open a new buffer', - nargs = '+', - complete = 'command', - } -) - - - - --- Toggle options {{{2 - -vim.keymap.set('n', 'T', '<Nop>') - -vim.keymap.set('n', 'Ta', '<Cmd>AutosaveToggle<CR>') -vim.keymap.set('n', 'Tb', ':<C-u>if &background == "dark" <Bar>set background=light <Bar>else <Bar>set background=dark <Bar>endif<CR>', { silent=true }) -vim.keymap.set('n', 'Tc', ':<C-u>set cursorcolumn! <Bar>set cursorline!<CR>', { silent=true }) -vim.keymap.set('n', 'Td', ':<C-u>if &diff <Bar>diffoff <Bar>else <Bar>diffthis <Bar>endif<CR>', { silent=true }) -vim.keymap.set('n', 'Te', '<Cmd>set expandtab!<CR>') -vim.keymap.set('n', 'Th', '<Cmd>set hlsearch!<CR>') -vim.keymap.set('n', 'Tn', '<Cmd>set number!<CR>') -vim.keymap.set('n', 'Ts', '<Cmd>set spell!<CR>') -vim.keymap.set('n', 'T8', ':<C-u>if &textwidth ==# 80 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=80 <Bar>endif<CR>', { silent=true }) -vim.keymap.set('n', 'T0', ':<C-u>if &textwidth ==# 100 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=100 <Bar>endif<CR>', { silent=true }) -vim.keymap.set('n', 'T2', ':<C-u>if &textwidth ==# 120 <Bar>set textwidth=0 <Bar>else <Bar>set textwidth=120 <Bar>endif<CR>', { silent=true }) -vim.keymap.set('n', 'Tw', '<Cmd>set wrap!<CR>') - -vim.keymap.set('n', 'TA', 'Ta', { remap=true }) -vim.keymap.set('n', 'TB', 'Tb', { remap=true }) -vim.keymap.set('n', 'TC', 'Tc', { remap=true }) -vim.keymap.set('n', 'TD', 'Td', { remap=true }) -vim.keymap.set('n', 'TE', 'Te', { remap=true }) -vim.keymap.set('n', 'TH', 'Th', { remap=true }) -vim.keymap.set('n', 'TN', 'Tn', { remap=true }) -vim.keymap.set('n', 'TS', 'Ts', { remap=true }) -vim.keymap.set('n', 'TW', 'Tw', { remap=true }) - - - --- Increment/decrement numbers {{{2 - --- 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 {{{2 - -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 - -function vimrc.fn.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() vimrc.fn.open_scratch() end, - { - desc = 'Open a *scratch* buffer', - } -) - -vim.keymap.set('n', '<Space>s', '<Cmd>Scratch<CR>') - - - --- Disable unuseful or dangerous mappings. {{{2 - --- Disable Select mode. -vim.keymap.set('n', 'gh', '<Nop>') -vim.keymap.set('n', 'gH', '<Nop>') -vim.keymap.set('n', 'g<C-h>', '<Nop>') - --- Disable Ex mode. -vim.keymap.set('n', 'Q', '<Nop>') -vim.keymap.set('n', 'gQ', '<Nop>') - -vim.keymap.set('n', 'ZZ', '<Nop>') -vim.keymap.set('n', 'ZQ', '<Nop>') - - --- Help {{{2 - --- Search help. -vim.keymap.set('n', '<C-h>', ':<C-u>SmartOpen help<Space>') - - - --- For writing Vim script. {{{2 - -vim.keymap.set('n', 'XV', '<Cmd>SmartTabEdit $MYVIMRC<CR>') - --- See |numbered-function|. -vim.keymap.set('n', 'XF', ':<C-u>function {<C-r>=v:count<CR>}<CR>', { silent=true }) - -vim.keymap.set('n', 'XM', '<Cmd>messages<CR>') - - - - - - - --- Abbreviations {{{2 - -vimrc.iabbrev('TOOD', 'TODO') -vimrc.iabbrev('retrun', 'return') -vimrc.iabbrev('reutrn', 'return') -vimrc.iabbrev('tihs', 'this') - - -vimrc.cabbrev('S', '%s') - - - --- Misc. {{{2 - -vim.keymap.set('o', 'gv', ':<C-u>normal! gv<CR>', { silent=true }) - --- Swap : and ;. -vim.keymap.set('n', ';', ':') -vim.keymap.set('n', ':', ';') -vim.keymap.set('x', ';', ':') -vim.keymap.set('x', ':', ';') -vim.keymap.set('n', '@;', '@:') -vim.keymap.set('x', '@;', '@:') -vim.keymap.set('!', '<C-r>;', '<C-r>:') - - --- Since <ESC> may be mapped to something else somewhere, it should be :map, not --- :noremap. -vim.keymap.set('!', 'jk', '<ESC>', { remap=true }) - - - -vim.keymap.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 -]]) -vim.keymap.set('n', 'go', F.Vimrc_insert_black_line_below, { expr = true }) -vim.keymap.set('n', 'gO', F.Vimrc_insert_black_line_above, { expr = true }) - - -vim.keymap.set('n', '<Space>w', '<Cmd>update<CR>') - -vim.keymap.set('n', 'Z', '<Cmd>wqall<CR>', { nowait = true }) - - --- `s` is used as a prefix key of plugin sandwich and hop. -vim.keymap.set('n', 's', '<Nop>') -vim.keymap.set('x', 's', '<Nop>') - - --- Commands {{{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 -vim.api.nvim_create_user_command( - 'Reverse', - 'keeppatterns <line1>,<line2>g/^/m<line1>-1', - { - desc = 'Reverse lines', - bar = true, - range = '%', - } -) - - - -function vimrc.fn.smart_tabedit(mods, args) - 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 - - --- If the current buffer is empty, open a file with the current window; --- otherwise open a new tab. -vim.api.nvim_create_user_command( - 'SmartTabEdit', - function(opts) vimrc.fn.smart_tabedit(opts.mods, opts.args) end, - { - desc = 'Smartly open a file', - nargs = '*', - complete = 'file', - } -) - - - --- Appearance {{{1 - --- Color scheme {{{2 - -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 {{{2 - -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 {{{2 - -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 - - - --- Plugins {{{1 - --- Disable standard plugins. {{{2 - -G.loaded_gzip = 1 -G.loaded_matchparen = 1 -G.loaded_netrw = 1 -G.loaded_netrwPlugin = 1 -G.loaded_spellfile_plugin = 1 -G.loaded_tarPlugin = 1 -G.loaded_zipPlugin = 1 - - - --- Load and configure third-party plugins. {{{2 - -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, -}) +--[========================================[-- +-- -- +-- _ _ _ _ -- +-- (_)_ __ (_) |_ | |_ _ __ _ -- +-- | | '_ \| | __| | | | | |/ _` | -- +-- | | | | | | |_ _| | |_| | (_| | -- +-- |_|_| |_|_|\__(_)_|\__,_|\__,_| -- +-- -- +--]========================================]-- + +require('init.00-bootstrap') +require('init.01-options') +require('init.02-commands') +require('init.03-autocmds') +require('init.04-mappings') +require('init.05-appearance') +require('init.06-plugins') 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, +}) |
