"        _
" __   _(_)_ __ ___  _ __ ___
" \ \ / / | '_ ` _ \| '__/ __|
"  \ V /| | | | | | | | | (__
" (_)_/ |_|_| |_| |_|_|  \___|
"
" @kawarimidoll
" https://twitter.com/kawarimidoll
" https://github.com/kawarimidoll

if has('nvim')
  lua if vim.loader then vim.loader.enable() end
else
  set nocompatible
  set encoding=utf-8
  scriptencoding utf-8
  filetype plugin indent on
  packadd matchit
  " syntax enable
  autocmd FileType * ++once ++nested syntax enable
  let g:qf_disable_statusline = 1
endif
" language ja_JP.UTF-8

" let g:did_install_default_menus = 1
" let g:did_install_syntax_menu   = 1
" let g:did_indent_on             = 1
" " load ftplugin to set commentstring
" " let g:did_load_ftplugin         = 1
" let g:loaded_2html_plugin       = 1
" let g:loaded_getscript          = 1
" let g:loaded_getscriptPlugin    = 1
" let g:loaded_gzip               = 1
" let g:loaded_logiPat            = 1
" let g:loaded_logipat            = 1
" let g:loaded_man                = 1
" let g:loaded_matchit            = 1
" let g:loaded_matchparen         = 1
" let g:loaded_netrw              = 1
" let g:loaded_netrwFileHandlers  = 1
" let g:loaded_netrwPlugin        = 1
" let g:loaded_netrwSettings      = 1
" let g:loaded_remote_plugins     = 1
" let g:loaded_rrhelper           = 1
" let g:loaded_shada_plugin       = 1
" let g:loaded_spellfile_plugin   = 1
" let g:loaded_sql_completion     = 1
" let g:loaded_tar                = 1
" let g:loaded_tarPlugin          = 1
" let g:loaded_tutor_mode_plugin  = 1
" let g:loaded_vimball            = 1
" let g:loaded_vimballPlugin      = 1
" let g:loaded_zip                = 1
" let g:loaded_zipPlugin          = 1
" let g:skip_defaults_vim         = 1
" let g:skip_loading_mswin        = 1
" let g:vimsyn_embed              = 1

" avoid to set expandtab in default ftplugin
let g:markdown_recommended_style = 0

" {{{ Keymap
" https://zenn.dev/kawarimidoll/articles/513d603681ece9
function! s:keymap(force_map, modes, ...) abort
  let arg = join(a:000, ' ')
  let cmd = a:force_map ? 'map' : 'noremap'
  for mode in split(a:modes, '.\zs')
    if stridx('nvsxoilct', mode) < 0
      echoerr '[keymap] invalid mode is detected: ' mode arg
      continue
    endif
    execute mode .. cmd arg
  endfor
endfunction
command! -nargs=+ -bang Keymap call s:keymap(<bang>0, <f-args>)
" }}}

" perl -e 'print sort { length($a) <=> length($b) } <>' /usr/share/dict/words > ~/.cache/vim/sorted_words
set dictionary+=~/.cache/vim/sorted_words

let s:word_1000_dict_path = expand('~/.cache/vim/google-10000-english-no-swears.txt')
let s:word_1000_dict_url = 'https://raw.githubusercontent.com/first20hours/'
      \ .. 'google-10000-english/master/google-10000-english-no-swears.txt'
execute 'set dictionary+=' .. s:word_1000_dict_path
command! -bang DownloadDictFile call mi#utils#download(s:word_1000_dict_url, s:word_1000_dict_path, <bang>0)

command! -nargs=+ -bang -complete=expression NotifyShow call mi#notify#show([<args>], <bang>0 ? 3 : 2)
command! -bang NotifyHistory call mi#notify#history(<bang>0)
command! NotifyForget call mi#notify#forget()

" function! s:look_completefunc() abort
"   let col = col('.')
"   if col < 2
"     return ''
"   endif
"   let line = getline('.')[0:col-2]
"   let start_idx = matchend(line, '.*\A')
"   if start_idx < 0
"     let start_idx = 0
"   endif
"   let word = line[start_idx:]
"   let results = systemlist($"look {word}")
"   call sort(results, {a,b -> strlen(a) - strlen(b)})
"   call complete(start_idx + 1, results)
"   return ''
" endfunction
" inoremap <c-x><c-k> <c-r>=<sid>look_completefunc()<cr>

" alc represents auto look completeion
let s:alc_enable = v:false
function! s:toggle_alc() abort
  let s:alc_enable = !s:alc_enable
  augroup vimrc_alc
    autocmd!
    if s:alc_enable
      autocmd TextChangedI * echomsg getline('.')[col('.')-1]
      " autocmd TextChangedI *
      "       \   if !pumvisible() && getline('.')[col('.')-1] =~ '\a'
      "       \ |   call feedkeys("\<c-x>\<c-k>", 'm')
      "       \ | else
      "       \ | echo 'not fit'
      "       \ | endif
    endif
  augroup END
  echo 'look auto completion:' (s:alc_enable ? 'enabled' : 'disabled')
endfunction
command! ToggleAutoLookCompletion call s:toggle_alc()

let s:dotfiles_dict_path = expand('~/dotfiles/.config/cspell/dotfiles.txt')
execute 'set dictionary+=' .. s:dotfiles_dict_path

set ambiwidth=single
set breakindent
set breakindentopt=min:50,shift:4,sbr,list:-1
set cedit=\<C-Q>
" set clipboard&
" set clipboard^=unnamed
" set clipboard^=unnamedplus
set complete=.,w,k,b,u
set completeopt=menuone,noselect,fuzzy
set expandtab
set fileencodings=ucs-bom,utf-8,iso-2022-jp-3,euc-jp,cp932
set fillchars+=vert:│,eob:\\x20
set foldcolumn=0
set formatoptions+=jmM1
set history=2000
set ignorecase
set infercase
set laststatus=2
set lazyredraw
set linebreak
set list
set listchars=tab:^-,trail:~,extends:»,precedes:«,nbsp:%
set nobackup
set nofixeol
set nomodeline
set nonumber
set noshowmode
set noswapfile
set nowritebackup
set runtimepath+=~/dotfiles/.vim
set runtimepath+=~/dotfiles/.vim/after
set scrolloff=5
set shiftround
set shiftwidth=2
set shortmess+=scCI
set showbreak=↪
set showtabline=0
set signcolumn=number
set smartcase
set smartindent
set softtabstop=2
set spelllang+=cjk
set spelloptions=camel
set splitbelow
set splitright
set switchbuf+=usetab
set tabstop=2
set termguicolors
set title
set updatetime=300
set whichwrap=b,s,h,l,<,>,[,],~
set wildmode=longest,full
set wrap
set wrapscan
if executable('rg')
  set grepprg=rg\ --vimgrep\ --trim\ --hidden\ --glob=!.git
  set grepformat=%f:%l:%c:%m
endif

if has('nvim')
  set inccommand=split
  set scrollback=2000
else
  " :h vim-differences
  " :h nvim-defaults
  set autoindent
  set autoread
  set background=dark
  set backspace=indent,eol,start
  set belloff=all
  set completeopt+=popuphidden
  set display=lastline
  set hidden
  set hlsearch
  set incsearch
  set nojoinspaces
  set noruler
  set nostartofline
  set showcmd
  set smartcase
  set smarttab
  set ttimeout
  set ttimeoutlen=50
  set ttyfast
  set viminfo='20,<50,s10,h
  set viminfofile=~/.vim/viminfo
  set wildmenu
  set wildoptions=pum,tagfile

  nnoremap Y y$
  nnoremap <C-l> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-l><CR>
  inoremap <C-u> <C-g>u<C-u>
  inoremap <C-w> <C-g>u<C-w>
  nnoremap & :&&<CR>

  nnoremap <C-a> <Cmd>call mi#dial#increment(v:count1)<CR>
  nnoremap <C-x> <Cmd>call mi#dial#decrement(v:count1)<CR>

  command! LazyGit tab terminal ++close ++norestore lazygit

  Keymap nx <expr> gc mi#comment#operator_toggle()
  nnoremap <expr> gcc mi#comment#operator_toggle() .. '_'

  " Keymap nx <expr> sa mi#surround#add()
  Keymap nx <expr> sa mi#surround#operator('add')
  nnoremap <expr> sd mi#surround#operator('delete') .. ' '
  nnoremap <expr> sr mi#surround#operator('replace') .. ' '

  let g:mi#ft#multiline = 1
  Keymap nx f <cmd>call mi#ft#smart('f')<CR>
  Keymap nx F <cmd>call mi#ft#smart('F')<CR>
  Keymap nx t <cmd>call mi#ft#smart('t')<CR>
  Keymap nx T <cmd>call mi#ft#smart('T')<CR>
  Keymap nx ; <cmd>call mi#ft#repeat(';')<CR>
  Keymap nx , <cmd>call mi#ft#repeat(',')<CR>
  onoremap <expr> f mi#ft#smart_expr('f')
  onoremap <expr> F mi#ft#smart_expr('F')
  onoremap <expr> t mi#ft#smart_expr('t')
  onoremap <expr> T mi#ft#smart_expr('T')
  onoremap <expr> ; mi#ft#repeat_expr(';')
  onoremap <expr> , mi#ft#repeat_expr(',')
  noremap sf f
  noremap sF F
  noremap st t
  noremap sT T
  noremap s; ;
  noremap s, ,

  nnoremap . <cmd>call mi#common#dot_repeat()<cr>

  nnoremap <c-e> <cmd>call mi#window#resize()<cr>

  " let s:help_dir = expand('~/.vim/vimdoc-ja')
  " execute 'set runtimepath+=' .. s:help_dir
  " function! s:ja_doc_update() abort
  "   if isdirectory(s:help_dir)
  "     echo '[ja_doc] pull latest version...'
  "     echo system(printf('git -C %s reset --hard', s:help_dir))
  "     echo system(printf('git -C %s pull --rebase', s:help_dir))
  "     echo system(printf('git -C %s gc --prune=now', s:help_dir))
  "   else
  "     echo '[ja_doc] clone latest version...'
  "     echo system(printf('git clone --depth 1 https://github.com/vim-jp/vimdoc-ja.git %s', s:help_dir))
  "   endif
  "   execute 'helptags' s:help_dir .. '/doc'
  "   echo '[ja_doc] successfully updated.'
  " endfunction
  " command! JaDocUpdate call s:ja_doc_update()

  " prev/next use
  nnoremap [u *2Nzz<cmd>nohlsearch<cr>
  nnoremap ]u *zz<cmd>nohlsearch<cr>

  " word search with keep positions
  " https://twitter.com/Bakudankun/status/1207057884581900289
  " `doautocmd CursorMoved` is required to show searchprop
  nnoremap <silent><expr> * v:count ? '*' :
        \ '<cmd>silent execute "keepjump normal! *" <bar> call winrestview('
        \ .. string(winsaveview()) .. ')<cr><cmd>doautocmd CursorMoved<cr>'
  nnoremap <silent><expr> # v:count ? '#' :
        \ '<cmd>silent execute "keepjump normal! #" <bar> call winrestview('
        \ .. string(winsaveview()) .. ')<cr><cmd>doautocmd CursorMoved<cr>'

  " :h termcap-cursor-shape@en
  " https://vim.fandom.com/wiki/Change_cursor_shape_in_different_modes
  if &term =~ 'xterm' || &term == 'win32'
    " Use DECSCUSR escape sequences
    " vertical bar in insert mode
    let &t_SI = "\e[5 q"
    " underline in replace mode
    let &t_SR = "\e[3 q"
    " block in normal mode
    let &t_EI = "\e[1 q"
    " change shape enter / escape termcap
    let &t_ti ..= "\e[1 q"
    let &t_te ..= "\e[0 q"
    "  0 -> default (depends on terminal, normally blinking block)
    "  1 -> blinking block
    "  2 -> solid block
    "  3 -> blinking underscore
    "  4 -> solid underscore
    "  5 -> blinking vertical bar
    "  6 -> solid vertical bar
  endif

endif

let g:my_vimrc = $MYVIMRC
let g:vimrc_snr = expand('<SID>')

command! RcEdit execute 'edit' g:my_vimrc
command! RcReload write | execute 'source' g:my_vimrc | nohlsearch | redraw | echo g:my_vimrc .. ' is reloaded.'

function! s:copy_path(target, with_lnum) abort
  let expr = '%'
  if a:target == 'full path'
    let expr ..= ':p'
  elseif a:target == 'dir name'
    let expr ..= ':h'
  elseif a:target == 'file name'
    let expr ..= ':t'
  endif

  let @*=expand(expr) .. (a:with_lnum ? ':' .. line('.') : '')
  echo 'copy ' .. a:target .. (a:with_lnum ? ' with line num' : '')
endfunction
command! -bang CopyFullPath     call s:copy_path('full path', <bang>0)
command! -bang CopyDirName      call s:copy_path('dir name', <bang>0)
command! -bang CopyFileName     call s:copy_path('file name', <bang>0)
command! -bang CopyRelativePath call s:copy_path('relative path', <bang>0)
command! CopyLastCmd let @* = @:

command! -nargs=+ -bang Grep  call mi#qf#async_grep(<q-args>, {'add': <bang>0})
command! -nargs=+ -bang GrepF call mi#qf#async_grep(<q-args>, {'add': <bang>0, 'fixed': 1})
command! -nargs=+ -bang LGrep  call mi#qf#async_grep(<q-args>, {'add': <bang>0, 'loc': 1})
command! -nargs=+ -bang LGrepF call mi#qf#async_grep(<q-args>, {'add': <bang>0, 'loc': 1, 'fixed': 1})

command! -nargs=1 -complete=file VDiff vertical diffsplit <args>
command! VDiffAlt vertical diffsplit #

command! SudoWrite write !sudo tee > /dev/null %

nnoremap <silent> mm <Cmd>call mi#mark#auto_set()<CR>
nnoremap <silent> m, <Cmd>call mi#mark#jump_to_last()<CR>

nnoremap <silent> RR R
nnoremap <expr> R mi#operator#replace()
nnoremap <expr> J mi#operator#join()
nnoremap <expr> JJ mi#operator#join() .. 'j'
xnoremap <expr> J mi#operator#join()

" like helix
nnoremap U <c-r>

" kensaku
let g:kensaku#args = '--dict ~/.local/share/jsmigemo/no-abbrev-compact-dict'
nnoremap J/ <cmd>call mi#kensaku#start()<cr>
nnoremap J? <cmd>call mi#kensaku#start({ 'init': mi#kensaku#last_query() })<cr>
nnoremap n <cmd>call mi#kensaku#next(0)<cr>
nnoremap N <cmd>call mi#kensaku#next(1)<cr>

" https://zenn.dev/yahomi_dev/articles/216231f2902f32
function! s:split_line() abort
  normal! mz
  call execute('s/\v\{\zs\s?|,\zs\s|\s?\ze\}/\r/g', 'silent!')
  normal! =`z
  nohlsearch
endfunction
nnoremap Jp <cmd>call <sid>split_line()<cr>

" https://zenn.dev/mattn/articles/83c2d4c7645faa
nmap gj gj<SID>g
nmap gk gk<SID>g
nnoremap <script> <SID>gj gj<SID>g
nnoremap <script> <SID>gk gk<SID>g
nmap <SID>g <Nop>

noremap h <cmd>call mi#hjkl#h(v:count1)<cr>
noremap l <cmd>call mi#hjkl#l(v:count1)<cr>
" noremap j <cmd>call mi#hjkl#j(v:count1)<cr>
" noremap k <cmd>call mi#hjkl#k(v:count1)<cr>
noremap sj <cmd>call mi#hjkl#j(v:count1, v:true)<cr>
noremap sk <cmd>call mi#hjkl#k(v:count1, v:true)<cr>

" noremap gj j
" noremap gk k
noremap gV `[v`]
" https://github.com/yuki-yano/zero.vim/blob/main/plugin/zero.vim
noremap <silent><expr> H (mi#utils#not_q() && getline('.')[:col('.')-2] =~# '^\s\+$')? '0' : '^'
" https://github.com/yuki-yano/dotfiles/blob/c56b219116de1693c45c20b03b109497896d5b16/.vimrc#L602-L605
nnoremap <expr> i len(getline('.')) ? 'i' : '"_cc'
nnoremap <expr> A len(getline('.')) ? 'A' : '"_cc'
noremap L $
map M %
map gM g%
noremap gm gM
nnoremap / /\v
nnoremap ? /\V

nnoremap F<cr> {
nnoremap f<cr> }

nnoremap <script> <c-g> 2<c-g><sid>(cg)
nnoremap <sid>(cg)<c-g> <cmd>echomsg 'hello'<cr>
nnoremap <sid>(cg)e <cmd>echomsg 'hello'<cr>
nnoremap <sid>(cg)p <cmd>CopyFullPath<cr>
nnoremap <sid>(cg)P <cmd>CopyFullPath!<cr>
nnoremap <sid>(cg)d <cmd>CopyDirName<cr>
nnoremap <sid>(cg)D <cmd>CopyDirName!<cr>
nnoremap <sid>(cg)f <cmd>CopyFileName<cr>
nnoremap <sid>(cg)F <cmd>CopyFileName!<cr>
nnoremap <sid>(cg)r <cmd>CopyRelativePath<cr>
nnoremap <sid>(cg)R <cmd>CopyRelativePath!<cr>
nnoremap <sid>(cg)c <cmd>CopyLastCmd<cr>
nnoremap <sid>(cg)C <cmd>CopyLastCmd!<cr>
" nnoremap <script> <sid><c-g><c-g> <cmd>let @*=expand("%:~:.")<cr>
nmap <sid>(cg) <nop>

nnoremap s <NOP>
if has('nvim')
  " in neovim, :source can read lua file
  nnoremap so <Cmd>execute 'source' (filereadable(@%) ? @% : '')<Bar>nohlsearch<CR>
else
  function s:source() abort
    let fname = filereadable(@%) ? @% : ''
    let is_lua = &filetype == 'lua' || fname =~ 'lua$'
    if !is_lua
      execute 'source' fname
    elseif !empty(fname)
      execute 'luafile' fname
    else
      execute 'lua' getline(1, '$')->join(' ')
    endif
    " nohlsearch
  endfunction
  nnoremap so <Cmd>call <sid>source()<Bar>nohlsearch<CR>
endif
nnoremap s/ :%s/\v
nnoremap s? :%s/\V
nnoremap S :%s/\V\<<C-r><C-w>\>//g<Left><Left>

" https://thinca.hatenablog.com/entry/q-as-prefix-key-in-vim
nnoremap <script><expr> q empty(reg_recording()) ? '<sid>(q)' : 'q'
nnoremap <sid>(q)q qq
nnoremap <script><expr> Q empty(reg_recording()) ? '@q' : 'q@q'
xnoremap <expr> Q ':global/^/normal Q' .. repeat('<left>', 9)

nnoremap <sid>(q)/ q/
nnoremap <sid>(q): q:
nnoremap <sid>(q)a <Cmd>call mi#qf#add_curpos()<CR>
nnoremap <sid>(q)c <Cmd>call mi#qf#toggle()<CR>
nnoremap <sid>(q)d <Cmd>quit<CR>
nnoremap <sid>(q)D <Cmd>quitall!<CR>
nnoremap <sid>(q)g :<C-u>global/^/normal<Space>
nnoremap <sid>(q)h <Cmd>call mi#common#half_move('left', v:count1)<CR>
nnoremap <sid>(q)i <Cmd>call mi#common#half_move('center', v:count1)<CR>
nnoremap <sid>(q)j <Cmd>call mi#common#half_move('down', v:count1)<CR>
nnoremap <sid>(q)k <Cmd>call mi#common#half_move('up', v:count1)<CR>
nnoremap <sid>(q)l <Cmd>call mi#common#half_move('right', v:count1)<CR>
nnoremap <sid>(q)o <Cmd>only!<CR>
nnoremap <sid>(q)t <C-^>
nnoremap <sid>(q)x <Cmd>call mi#qf#clear()<CR>
nnoremap <sid>(q)w <Cmd>write<CR>
" reserve for neovim
if has('nvim')
  for c in ['d', 'z']
    execute printf('nnoremap <sid>(q)%s <Plug>(rc-q-%s)', c, c)
  endfor
endif

" function! s:sync_unnamed() abort
"   if empty(@")
"     let @" = @*
"   endif
" endfunction

nnoremap p ]p`]
nnoremap P [P`]
" nnoremap p <cmd>call <sid>sync_unnamed()<cr>]p`]
" nnoremap P <cmd>call <sid>sync_unnamed()<cr>[P`]
"nnoremap gp p
"nnoremap gP P
nnoremap x "_d
nnoremap X "_D
" nnoremap ' `

nmap <SID>[ <Nop>
nmap <SID>] <Nop>
function! s:neighbor_submode_map(key, prev_rhs, next_rhs) abort
  execute printf('nmap [%s %s<SID>[', a:key, a:prev_rhs)
  execute printf('nmap ]%s %s<SID>]', a:key, a:next_rhs)
  execute printf('nnoremap <script> <SID>[%s %s<SID>[', a:key, a:prev_rhs)
  execute printf('nnoremap <script> <SID>]%s %s<SID>]', a:key, a:next_rhs)
endfunction

call s:neighbor_submode_map('b', '<cmd>bprevious<cr>', '<cmd>bnext<cr>')
call s:neighbor_submode_map('q', '<cmd>call mi#qf#cycle_p()<cr>', '<cmd>call mi#qf#cycle_n()<cr>')
call s:neighbor_submode_map('w', '<c-w>w', '<c-w>W')

" nnoremap [m [`
" nnoremap ]m ]`
" nnoremap [p {
" nnoremap ]p }
nnoremap [B <Cmd>bfirst<CR>
nnoremap ]B <Cmd>blast<CR>
nnoremap <silent><expr> [Q '<Cmd>' .. v:count1 .. 'colder<CR>'
nnoremap <silent><expr> ]Q '<Cmd>' .. v:count1 .. 'cnewer<CR>'

" for 40% keyboard
nmap <BS> [
nmap ' ]

" https://twitter.com/mo_naqa/status/1467626946293284865
" nnoremap gf gFzz
" nnoremap gx :<C-u>!open <C-r><C-a>
command! -nargs=* SmartOpen call mi#open#smart_open(<q-args>)
Keymap nx gf <Cmd>call mi#open#smart_open()<CR>

"for c in g:mi#const#alpha_all->split('\zs')
"  execute 'nnoremap <Space>' .. c '<NOP>'
"endfor
nmap <Space>8 *
nmap <Space>3 #
nnoremap <Space>b <Cmd>call mi#fzf#bufs()<CR>
nnoremap <Space>d <Cmd>call mi#buf#delete()<CR>
nnoremap <Space>D <Cmd>call mi#buf#delete({'force': v:true})<CR>
nnoremap <Space>f <Cmd>call mi#fzf#files()<CR>
nnoremap <Space>g <Cmd>copy.<CR>
nnoremap <Space>G <Cmd>copy-1<CR>
nnoremap <space>h <Cmd>call mi#fzf#mru()<CR>
nnoremap <Space>m <Cmd>marks<bar>execute $'normal! `{input("jump to: ")}'<CR>
nnoremap <expr> <Space>o mi#blank#below()
nnoremap <expr> <Space>O mi#blank#above()
nnoremap <expr> <Space>p exists('b:fmt_cmd')
      \ ? '<cmd>call mi#fmt#run(b:fmt_cmd)<cr>'
      \ : '<cmd>KeepCursor %call mi#common#trim()<cr>mzgg=G`z'
xnoremap <expr> <Space>p exists('b:fmt_cmd')
      \ ? '<cmd>call mi#fmt#run(b:fmt_cmd)<cr>'
      \ : ':call mi#common#trim()<cr>gv=gv'
nnoremap <Space>q <cmd>confirm quit<CR>
nnoremap <Space>r <Cmd>registers<bar>execute $'normal "{input("paste reg: ")}p'<CR>
nnoremap <Space>Q <cmd>quitall!<CR>
nnoremap <Space>w <cmd>w<CR>
nnoremap <Space>wq <cmd>confirm wq<CR>
nnoremap <Space>; @:
nnoremap <Space>/ :<C-u>Grep<Space>
nnoremap <Space>? :<C-u>GrepF <C-r><C-w>
xnoremap <Space>? "zy:<C-u>GrepF <C-r>z
nnoremap <silent><expr> <C-k> $'<Cmd>move-1-{v:count1}<CR>=l'
nnoremap <silent><expr> <C-j> $'<Cmd>move+{v:count1}<CR>=l'
nnoremap <Space>L <Cmd>LazyGit<CR>

" https://zenn.dev/kawarimidoll/articles/17dad86545cbb4
nnoremap <C-f> <Cmd>set scroll=0<CR><Cmd>execute 'normal!' repeat('<C-d>', v:count1 * 2)<CR>
nnoremap <C-b> <Cmd>set scroll=0<CR><Cmd>execute 'normal!' repeat('<C-u>', v:count1 * 2)<CR>

" command
" cnoremap <expr> s getcmdtype() == ':' && getcmdline() == 's' ? '<BS>%s/' : 's'
cnoremap <expr> % getcmdtype() == ':' && getcmdpos() > 2 && getcmdline()[getcmdpos()-2] == '%' ?
      \ '<BS>' .. expand('%:h') .. '/' : '%'
cnoremap <expr> / getcmdtype() == '/' ? '\/' : '/'
cnoremap <expr> <C-n> wildmenumode() ? '<C-n>' : '<Down>'
cnoremap <expr> <C-p> wildmenumode() ? '<C-p>' : '<Up>'
cnoremap <C-b> <Left>
cnoremap <C-f> <Right>
cnoremap <C-a> <Home>
cnoremap <C-e> <End>
cnoremap <C-d> <Del>
cmap <c-h> <bs>
" cnoremap <C-r><C-r> <cmd>call <sid>sync_unnamed()<cr><C-r><C-r>"
cnoremap <C-r><C-r> <C-r><C-r>"

" cnoremap <c-c> <home>Capture <cr>
cnoremap <expr> <C-k> repeat('<Del>', strchars(getcmdline()[getcmdpos() - 1:]))
cnoremap <s-cr> <cmd>call mi#cmdline#set_by_spec(extend(mi#cmdline#get_spec(), {'bang': v:true}))<cr><cr>
cnoremap <cr> <cmd>call mi#cmdline#proxy_convert()<cr><cr>
cnoremap <expr> <C-x> mi#magic#expr()

" {{{ AbbrevCmd
" expand abbreviations immediately in command-line
let s:abbrev_cmds = {}
function! s:abbrev_cmd(raw_rhs, cmd_lhs, ...) abort
  " backspace cmd_lhs
  let bs = repeat('<bs>', strchars(a:cmd_lhs) - 1)
  const rhs = a:raw_rhs ? string(bs) .. ".." .. join(a:000, ' ') :
        \ string(bs .. join(a:000, ' '))
  const [lhs_except_last, lhs_last] = matchlist(a:cmd_lhs, '^\(.*\)\(.\)$')[1:2]
  if !has_key(s:abbrev_cmds, lhs_last)
    let s:abbrev_cmds[lhs_last] = {}
  endif
  let s:abbrev_cmds[lhs_last][lhs_except_last] = rhs
  " can't use string() not to quote values
  const rhs_list = map(items(s:abbrev_cmds[lhs_last]), {_,val -> $"':{val[0]}':{val[1]}"})
  " range is not supported apart from visual selection
  const fmt = "cnoremap <expr> %s get({%s},getcmdtype()..getcmdline()->substitute(\"^'<,'>\",'',''),'%s')"
  execute printf(fmt, lhs_last, join(rhs_list, ','), lhs_last)
endfunction
command! -nargs=+ -bang AbbrevCmd call s:abbrev_cmd(<bang>0, <f-args>)
" }}}

AbbrevCmd cfi Cfilter
AbbrevCmd cap Capture
AbbrevCmd coc CopyLastCmd
AbbrevCmd cod CopyDirName
AbbrevCmd cof CopyFileName
AbbrevCmd coa CopyFullPath
AbbrevCmd cor CopyRelativePath
AbbrevCmd yep echo 'yep'
AbbrevCmd mes messages
AbbrevCmd mec messages clear
AbbrevCmd ec echo
AbbrevCmd ed edit
AbbrevCmd en enew
AbbrevCmd plgs PlugSync
AbbrevCmd plugs PlugSync
AbbrevCmd! sv 'saveas ' .. @%
AbbrevCmd! rn 'Rename ' .. @%
AbbrevCmd! ss '%s/' .. @/ .. '//g<Left><Left>'
" AbbrevCmd! ss '%s/' .. @/ .. "//g\<Left>\<Left>" olso ok
AbbrevCmd! gld 'global //d_<left><left><left>'

" cnoremap <expr> v getcmdtype() == ':' && getcmdline() == 's' ? '<c-u>saveas ' .. @% : 'v'
" cnoremap <expr> n getcmdtype() == ':' && getcmdline() == 'r' ? '<c-u>Rename ' .. @% : 'n'
" cnoremap <expr> s getcmdtype() == ':' && getcmdline() =~ '^plu\?g$' ? '<c-u>PlugSync' : 's'
cnoremap <expr> . getcmdtype() == '/' && getcmdpos() > 2 && getcmdline()[getcmdpos()-2] == ',' ?
      \ '<c-u>\<' .. substitute(getcmdline()[:-2], '\\v', '', 'gi') .. '\>' : '.'

" https://zenn.dev/monaqa/articles/2020-12-22-vim-abbrev
cnoreabbrev <expr> w getcmdtype() .. getcmdline() ==# ":'<,'>w" ? "\<C-u>w" : 'w'

" https://zenn.dev/vim_jp/articles/2023-06-30-vim-substitute-tips
" cnoreabbrev <expr> s getcmdtype() .. getcmdline() ==# ':s' ? [getchar(), ''][1] .. "%s/" .. escape(@/, '/') .. "//g<Left><Left>" : 's'

" " insert
" function s:next_or_cmp()
"   if pumvisible()
"     return feedkeys("\<c-n>", 'ni')
"   endif
"   call s:my_cmp()
" endfunction
" function s:my_cmp(phase = 0)
"   if pumvisible()
"     return
"   endif
"   let cmp_keys = ["\<c-f>", "\<c-v>", "\<c-n>", "\<c-k>"]
"   if &filetype != 'vim' && &filetype != 'help'
"     call remove(cmp_keys, 1)
"   endif
"   let col = col('.') - 2
"   let prev_char = col < 0 ? '' getline('.')[col]
"   if empty(prev_char) || prev_char =~ '\s'
"     call remove(cmp_keys, 0)
"   endif
"   let next_cmp = get(cmp_keys, a:phase, '')
"   if empty(next_cmp)
"     return feedkeys("\<c-n>", 'ni')
"   endif
"   let recall = $"\<cmd>call {expand('<SID>')}my_cmp({a:phase + 1})\<cr>"
"   call feedkeys($"\<c-x>{next_cmp}{recall}", 'ni')
" endfunction
" inoremap <tab> <cmd>call <sid>next_or_cmp()<cr>
" inoremap <c-x><c-y> <cmd>call <sid>my_cmp()<cr>

imap <c-h> <bs>
" inoremap <silent> <C-r><C-r> <cmd>call <sid>sync_unnamed()<cr><C-r><C-r>"
inoremap <silent> <C-r><C-r> <C-r><C-r>"
inoremap <C-k> <C-o>D
inoremap <expr> <Tab>   pumvisible() ? '<C-n>' : '<C-t>'
inoremap <expr> <S-Tab> pumvisible() ? '<C-p>' : '<C-d>'
inoremap <C-g><C-u> <esc>gUvbgi
inoremap <C-g><C-l> <esc>guvbgi
inoremap <right> <c-g>U<right>
inoremap <left> <c-g>U<left>
inoremap <home> <c-g>U<home>
inoremap <end> <c-g>U<end>
imap <c-f> <right>
imap <c-b> <left>
imap <c-a> <home>
imap <c-e> <end>
inoremap <c-j> <del>
inoremap <c-x><c-v> <c-x><c-v><c-n>

" https://zenn.dev/kawarimidoll/articles/262785e8ca05b0
inoremap <expr> <c-g><c-f> &completeopt =~ 'fuzzy'
      \    ? '<cmd>set completeopt-=fuzzy<cr>a<bs>'
      \    : '<cmd>set completeopt+=fuzzy<cr>a<bs>'

" https://zenn.dev/kawarimidoll/articles/54e38aa7f55aff
inoremap <expr> /
      \ complete_info(['mode']).mode == 'files' && complete_info(['selected']).selected >= 0
      \   ? '<c-x><c-f>'
      \   : '/'
inoremap <expr> .
      \ complete_info(['mode']).mode == 'files' && complete_info(['selected']).selected >= 0
      \   ? '.<c-x><c-f>'
      \   : '.'

" sticky-shift
" https://vim-jp.org/vim-users-jp/2009/08/09/Hack-54.html
function s:sticky_shift() abort
  let char = getcharstr()[0]
  return char ==# ' ' ?
        \   ';'
        \ : char =~# '^\p' ?
        \   mi#utils#upper_key(char)
        \ : ''
endfunction
noremap! <expr> ; <sid>sticky_shift()

function s:caps() abort
  if exists('#capslock#KeyInputPre')
    autocmd! capslock
    return
  endif
  augroup capslock
    autocmd!
    autocmd KeyInputPre * if v:char =~# '^\l'
          \ |   let v:char = mi#utils#upper_key(v:char)
          \ | endif
    autocmd CmdlineLeave,InsertLeave * autocmd! capslock
  augroup END
endfunction
noremap! C <Cmd>call <SID>caps()<CR>

" https://zenn.dev/vim_jp/articles/b4294351def1ba
function! s:symbol_cmp() abort
  let triggers = 'hjklnmfdsavcuiorew'->split('\zs')
  let comp_list = b:symbol_list->copy()
        \ ->map({i, v -> {'word': v, 'menu': get(triggers, i, '')}})
  call complete(col('.')-1, comp_list)
  augroup symbolselect
    autocmd!
    autocmd KeyInputPre * call s:symbol_select()
    autocmd ModeChanged * autocmd! symbolselect
  augroup END
endfunction

function! s:symbol_select() abort
  let items = complete_info(['items']).items
  let idx = indexof(items, {_,v -> get(v, 'menu', '') ==# v:char})
  if idx >= 0
    let v:char = "\<bs>"
    call feedkeys(items[idx].word, 'ni')
  endif
endfunction

inoremap <expr> , exists('b:symbol_list')
      \ ? ',<cmd>call <sid>symbol_cmp()<cr>'
      \ : ','

" function! s:imap_up() abort
"   if line('.') == 1
"     call feedkeys("\<c-g>U\<home>", 't')
"     return
"   endif
"   if exists('g:mi#dot_repeating')
"     call feedkeys("\<up>", 't')
"     return
"   endif
"   call appendbufline(0, line('.'), getline('.'))
"   call setbufline(0, line('.'), getline(line('.')-1))
"   call deletebufline(0, line('.')-1)
" endfunction
" function! s:imap_down() abort
"   if line('.') == line('$')
"     call feedkeys("\<c-g>U\<end>", 't')
"     return
"   endif
"   if exists('g:mi#dot_repeating')
"     call feedkeys("\<down>", 't')
"     return
"   endif
"   call appendbufline(0, line('.')-1, getline('.'))
"   call setbufline(0, line('.'), getline(line('.')+1))
"   call deletebufline(0, line('.')+1)
" endfunction
" if !has('nvim')
"   inoremap <c-l> <c-@>
"   inoremap <up> <cmd>call <sid>imap_up()<cr>
"   inoremap <down> <cmd>call <sid>imap_down()<cr>
" endif

let s:ft_expand_targets = {
      \ 'javascript': {
      \ ';p': "console.log()\<left>",
      \ ';if': "if() {}\<left>\<cr>\<c-o>\<up>\<home>\<c-o>f)",
      \ },
      \ 'vim': {
      \ ';p': 'echomsg ',
      \ ';if': "if\<cr>endif\<c-o>\<up>\<end> ",
      \ ';fun': "function!  abort\nendfunction\<up>\<c-o>E\<right>",
      \ },
      \ }
let s:expand_targets = {
      \ '%': { ->@% },
      \ ';p': "print()\<left>",
      \ }
let s:ft_aliases = {
      \ 'typescript': 'javascript',
      \ 'javascriptreact': 'javascript',
      \ 'typescriptreact': 'javascript',
      \ }
function! s:expand_snippet(fallback) abort
  const prev_idx = getcharpos('.')[2] - 2
  if prev_idx < 0
    return a:fallback
  endif
  const line = getline('.')
  const prev_cursor = line[:prev_idx]
  const prev_len = strlen(prev_cursor)
  const filetype = get(s:ft_aliases, &filetype, &filetype)
  const targets_current_ft = get(s:ft_expand_targets, filetype, {})
  const targets = extend(copy(s:expand_targets), targets_current_ft)
  for target_key in keys(targets)
    let match_idx = strridx(prev_cursor, target_key)
    if match_idx >= 0 && match_idx + strlen(target_key) == prev_len
      const replacement = type(targets[target_key]) == v:t_func ? targets[target_key]()
            \ : type(targets[target_key]) == v:t_list ? join(targets[target_key], "\<cr>")
            \ : targets[target_key]
      return repeat("\<bs>", strcharlen(target_key)) .. replacement
    endif
  endfor
  return a:fallback
endfunction
inoremap <expr> <c-x> <sid>expand_snippet("\<c-x>")

" visual
" xnoremap p <cmd>call <sid>sync_unnamed()<cr>P
xnoremap p P
xnoremap <silent> y y`]
xnoremap x "_x
xnoremap < <gv
xnoremap > >gv
" xmap <Tab>   >
" xmap <S-Tab> <
xnoremap <silent><C-k> :m'<-2<CR>gv=gv
xnoremap <silent><C-j> :m'>+1<CR>gv=gv
xnoremap S "zy:%s/<C-r>z//gce<Left><Left><Left><Left>
" ref: https://github.com/monaqa/dotfiles/blob/f214e9628cbac0803ad9d9bc2ad1582c696c4e6b/.config/nvim/scripts/keymap.vim#L63-L69
xnoremap * "zy/\V<C-r><C-r>=substitute(escape(@z, '/\'), '\_s\+', '\\_s\\+', 'g')<CR><CR>``
xnoremap # "zy?\V<C-r><C-r>=substitute(escape(@z, '/\'), '\_s\+', '\\_s\\+', 'g')<CR><CR>``
xnoremap R "zy:,$s//<C-r><C-r>=escape(@z, '/\&~')<CR>/gce<Bar>1,''-&&<CR>

xnoremap so :source<cr><cmd>nohlsearch<cr>gv

" operator
onoremap x d

" terminal
tnoremap <C-w><C-n> <C-\><C-n>
tnoremap <C-p> <Up>
tnoremap <C-n> <Down>

" https://qiita.com/monaqa/items/dcd43a53d3040293142a
let s:jp_digraphs = [
      \    ['aa', 'あ'], ['ii', 'い'], ['uu', 'う'], ['ee', 'え'], ['oo', 'お'],
      \    ['aA', 'ぁ'], ['iI', 'ぃ'], ['uU', 'ぅ'], ['eE', 'ぇ'], ['oO', 'ぉ'],
      \    ['Aa', 'ア'], ['Ii', 'イ'], ['Uu', 'ウ'], ['Ee', 'エ'], ['Oo', 'オ'],
      \    ['AA', 'ァ'], ['II', 'ィ'], ['UU', 'ゥ'], ['EE', 'ェ'], ['OO', 'ォ'],
      \    ['nn', 'ん'], ['Nn', 'ン'], ['j(', '('], ['j)', ')'], ['j[', '「'],
      \    ['j]', '」'], ['j{', '『'], ['j}', '』'], ['j<', '【'], ['j>', '】'],
      \    ['j,', '、'], ['j.', '。'], ['j!', '!'], ['j?', '?'], ['j:', ':'],
      \    ['j0', '0'], ['j1', '1'], ['j2', '2'], ['j3', '3'], ['j4', '4'],
      \    ['j5', '5'], ['j6', '6'], ['j7', '7'], ['j8', '8'], ['j9', '9'],
      \    ['j~', '〜'], ['j/', '・'], ['j ', ' '],
      \  ]
call digraph_setlist(s:jp_digraphs)
noremap f<C-j> f<C-k>j
noremap F<C-j> F<C-k>j
noremap t<C-j> t<C-k>j
noremap T<C-j> T<C-k>j

Keymap xo il <Cmd>call mi#textobject#line()<CR>
Keymap xo al <Cmd>call mi#textobject#line({'no_trim': 1})<CR>
Keymap xo iu <Cmd>call mi#textobject#fname()<CR>
Keymap xo iy <Cmd>call mi#textobject#lastpaste()<CR>
Keymap xo id <Cmd>call mi#textobject#datetime()<CR>
Keymap xo iA <Cmd>call mi#textobject#alphabet()<CR>
Keymap xo aA <Cmd>call mi#textobject#alphabet({'with_num': 1})<CR>
Keymap xo ie <Cmd>call mi#textobject#entire()<CR>
Keymap xo ae <Cmd>call mi#textobject#entire({'charwise': 1})<CR>
Keymap xo iS <Cmd>call mi#textobject#space()<CR>
Keymap xo aS <Cmd>call mi#textobject#space({'with_tab': 1})<CR>
Keymap xo io <Cmd>call mi#textobject#outline()<CR>
Keymap xo ao <Cmd>call mi#textobject#outline({'from_parent': 1, 'with_blank': 1})<CR>

if !has('nvim')
  Keymap xo i<space> iW
  Keymap xo ib <cmd>call mi#textobject#pair('i')<cr>
  Keymap xo ab <cmd>call mi#textobject#pair('a')<cr>
  Keymap xo iq <cmd>call mi#textobject#quote('i')<cr>
  Keymap xo aq <cmd>call mi#textobject#quote('a')<cr>
  " Keymap xo i<space> <cmd>call mi#textobject#between(' ', 0)<cr>
  Keymap xo a<space> <cmd>call mi#textobject#between(' ', 1)<cr>
  Keymap xo i<bar> <cmd>call mi#textobject#between("\<bar>", 0)<cr>
  Keymap xo a<bar> <cmd>call mi#textobject#between("\<bar>", 1)<cr>
  " does not works <bslash>...
  " Keymap xo i<bslash> <cmd>call mi#textobject#between("\<bslash>", 0)<cr>
  " Keymap xo a<bslash> <cmd>call mi#textobject#between("\<bslash>", 1)<cr>
  for i in range(33, 126)
    let c = nr2char(i)
    if c !~ '[[:punct:]]' || stridx('\|<>(){}[]"`''', c) >= 0
      continue
    endif
    execute 'Keymap xo i' .. c .. ' <cmd>call mi#textobject#between(''' .. c .. ''', 0)<cr>'
    execute 'Keymap xo a' .. c .. ' <cmd>call mi#textobject#between(''' .. c .. ''', 1)<cr>'
  endfor
  Keymap xo i? <cmd>call mi#textobject#input(0)<cr>
  Keymap xo a? <cmd>call mi#textobject#input(1)<cr>
endif

" {{{ Rename
" https://vim-jp.org/vim-users-jp/2009/05/27/Hack-17.html
command! -nargs=1 -complete=file Rename
      \ execute 'autocmd BufWritePost <buffer> ++once call delete("' .. expand('%') .. '")' |
      \ file <args>
" }}}

" {{{ EditProjectMru()
" https://zenn.dev/kawarimidoll/articles/057e0c26c6d6e3
" remove function after startup because it is only for `$ vim -c 'call EditProjectMru()'`
function! EditProjectMru() abort
  execute 'edit' get(mi#mru#list(v:true), 0, '')
  call timer_start(10, {->execute('delfunction! EditProjectMru')})
endfunction
" }}}

function! s:apply_user_highlight() abort
  highlight Reverse    cterm=reverse gui=reverse
  " highlight Pmenu      ctermfg=254 ctermbg=237 guifg=#e3e1e4 guibg=#37343a
  " highlight PmenuSel   ctermfg=237 ctermbg=254 guifg=#37343a guibg=#e3e1e4
  " highlight PmenuSbar  ctermbg=238 guibg=#423f46
  " highlight PmenuThumb ctermbg=255 guibg=#f8f8f2
  highlight Underlined ctermfg=NONE cterm=underline guifg=NONE gui=underline
  highlight ExtraWhitespace ctermbg=darkmagenta guibg=darkmagenta
  if !has('nvim')
    highlight! link StatusLine Comment
    highlight! link StatusLineNC Comment
    highlight! link VertSplit Comment
  endif
endfunction

" https://zenn.dev/hokorobi/articles/98f79339d7d114
function! s:syntax_off_on_heavy_file()
  if getfsize(@%) > 512 * 1000
    setlocal syntax=OFF
    NotifyShow 'syntax off: too heavy file.'
    if &filetype !=# 'help'
      call interrupt()
    endif
  endif
endfunction

command! -nargs=* -bang -complete=command Capture call mi#capture#run(<bang>0, <q-args>)

" https://github.com/thinca/config/blob/78a1d2d4725e2ff064722b48cea5b5f1c44f49f9/dotfiles/dot.vim/vimrc#L1407-L1408
command! -nargs=+ KeepCursor call mi#utils#keep_cursor(<q-args>)

function! s:visualize_autocmds()
  let events = [
        \ 'FileType', 'BufNewFile', 'BufEnter', 'BufRead', 'BufWinEnter', 'WinEnter',
        \ 'TabEnter', 'WinNew', 'TabNew', 'VimEnter',
        \ ]

  augroup v_au
    if exists('s:enable_v_au')
      autocmd!
      unlet! s:enable_v_au
      echo 'disable visualize autocmds'
    else
      for event in events
        execute printf('autocmd %s * echomsg ''au %s'' expand(''<amatch>'')', event, event)
      endfor
      let s:enable_v_au = 1
      echo 'enable visualize autocmds'
    endif
  augroup END
endfunction
command! VisualizeAutocmds call s:visualize_autocmds()
" enable when vim is starting
" call s:visualize_autocmds()

function! s:pin_help_buffer()
  let is_help = &buftype == 'help'
  if is_help
    let w:pin_buf_type = 'help'
    return
  endif

  let is_not_pinned = get(w:, 'pin_buf_type', '') != 'help'
  if is_not_pinned
    return
  endif

  let bufnr = bufnr()
  for winnr in range(1, winnr('$'))
    let win_id = win_getid(winnr)
    if getwinvar(win_id, 'pin_buf_type') == ''
      edit #
      call win_gotoid(win_id)
      execute 'buffer' bufnr
      return
    endif
  endfor
  echomsg 'all buffers are pinned.'
endfunction

" function! s:dprint(line1, line2)
"   if &filetype !~ '\v^%(%(type|java)script(react)?|markdown|jsonc?|toml|dockerfile)$'
"     echo '[dprint] filetype ' .. &filetype .. ' is not suppported'
"     return
"   endif
"   let curpos = getpos('.')[1:2]
"   execute printf('%s,%s!dprint fmt --stdin %s', a:line1, a:line2, @%)
"   call cursor(curpos[0], curpos[1])
" endfunction
" command! -range=% Dprint call s:dprint(<line1>, <line2>)

function! s:cased_substitute(query, line1, line2) abort
  let [from, to] = split(a:query, '/')
  execute printf('silent! %s,%ss/\C%s/%s/g', a:line1, a:line2, toupper(from), toupper(to))
  execute printf('silent! %s,%ss/\C%s/%s/g', a:line1, a:line2, toupper(from[0]) .. from[1:], toupper(to[0]) .. to[1:])
  execute printf('silent! %s,%ss/\C%s/%s/g', a:line1, a:line2, tolower(from), tolower(to)))
endfunction
command! -range -nargs=* CasedSubstitute call s:cased_substitute(<q-args>, <line1>, <line2>)
command! -range -nargs=* CS call s:cased_substitute(<q-args>, <line1>, <line2>)

function! s:enable_autosave() abort
  autocmd InsertLeave,TextChanged,BufLeave <buffer> silent update
endfunction
let s:memo_path = expand('~/.local/share/memo.md')
function! s:memo() abort
  if expand('%') ==# s:memo_path
    bdelete
    return
  endif
  execute 'edit' s:memo_path
  setlocal bufhidden=wipe noswapfile
  call s:enable_autosave()
endfunction
command! Memo call s:memo()
nnoremap mo <Cmd>Memo<CR>

if !has('nvim')
  function! s:silicon(line1, line2, args, highlight_lines)
    let cmd = ['silicon']
    if &filetype != ''
      call add(cmd, '--language')
      call add(cmd, &filetype)
    endif

    " TODO: use args
    let output = $'~/Downloads/silicon-{strftime("%Y%m%d-%H%M%S")}.png'
    call add(cmd, '--font')
    call add(cmd, 'UDEV Gothic 35NF')
    call add(cmd, '--output')
    call add(cmd, output)

    let in_opts =  { 'buf': bufnr() }
    if a:highlight_lines
      call add(cmd, '--highlight-lines')
      call add(cmd, a:line1 .. '-' .. a:line2)
    else
      let in_opts['top'] = a:line1
      let in_opts['bot'] = a:line2
    endif

    let opts = {
          \ 'in': in_opts,
          \ 'exit': {_->execute($'echo "[silicon] output to {output}"', '')}
          \ }
    call mi#job#start(cmd, opts)
  endfunction
  command! -range=% -bang Silicon call s:silicon(<line1>, <line2>, <q-args>, <bang>0)
endif

" skip autocmds after vim is started
if !has('vim_starting')
  finish
endif

augroup vimrc
  autocmd!

  let g:vimsyn_embed='l'
  autocmd FileType vim syn match luaParen "(\|)"

  autocmd BufEnter *.sh setlocal filetype=bash
  autocmd BufEnter * call s:pin_help_buffer()
  autocmd WinLeave,WinClosed * unlet! w:pin_buf_type
  autocmd BufEnter * call s:syntax_off_on_heavy_file()

  " Highlight extra whitespaces
  " https://zenn.dev/kawarimidoll/articles/450a1c7754bde6
  " u00A0 ' ' no-break space
  " u2000 ' ' en quad
  " u2001 ' ' em quad
  " u2002 ' ' en space
  " u2003 ' ' em space
  " u2004 ' ' three-per em space
  " u2005 ' ' four-per em space
  " u2006 ' ' six-per em space
  " u2007 ' ' figure space
  " u2008 ' ' punctuation space
  " u2009 ' ' thin space
  " u200A ' ' hair space
  " u200B '​' zero-width space
  " u3000 ' ' ideographic (zenkaku) space
  highlight ExtraWhitespace ctermbg=darkmagenta guibg=darkmagenta
  autocmd VimEnter,WinEnter * if !exists('w:highlight_extra_whitespace')
        \ | let w:highlight_extra_whitespace = matchadd('ExtraWhitespace', "[\u00A0\u2000-\u200B\u3000]")
        \ | endif
  autocmd ColorScheme * call s:apply_user_highlight()
  autocmd VimEnter * ++once call s:apply_user_highlight()

  " https://zenn.dev/kawarimidoll/articles/5490567f8194a4
  " this not works in ftplugin
  autocmd BufNew,BufEnter * if &filetype == 'markdown' | syntax match markdownError '\w\@<=\w\@=' | endif

  autocmd BufNewFile * ++nested call mi#open#reopen_with_lnum()

  " https://github.com/Shougo/shougo-s-github/blob/1327ad8140fc5f47ad15f316d55b2bed6dfd6ca5/vim/rc/options.rc.vim#L87-L97
  autocmd CmdlineEnter *
        \ : let s:save_iskeyword = &l:iskeyword
        \ | setlocal iskeyword+=.
        \ | setlocal iskeyword+=-
        \ | setlocal iskeyword+=+
        \ | setlocal iskeyword-=/
        \ | setlocal iskeyword-=\:
        \ | setlocal iskeyword-=:
  autocmd CmdlineLeave *
        \ let &l:iskeyword = s:save_iskeyword

  if has('nvim')
    " https://jdhao.github.io/2020/05/22/highlight_yank_region_nvim/
    autocmd TextYankPost * silent! lua vim.highlight.on_yank({ timeout=500 })
    autocmd TermOpen * if exists('w:highlight_extra_whitespace') && exists('b:terminal_job_id')
          \ | silent! call matchdelete(w:highlight_extra_whitespace)
          \ | endif
  else
    " https://github.com/NI57721/dotfiles/blob/a695ab1d29465dc54254c933f13ac46911a07afd/.vimrc#L255-L260
    autocmd Filetype xml,html inoremap <buffer><expr> </
          \ &completeopt=~'noinsert\\|noselect' ? '</<c-x><c-o><c-n><c-y>' : '</<c-x><c-o><c-y>'

    " autocmd WinEnter,BufEnter * setlocal statusline=%!mi#statusline#active()
    " autocmd WinLeave,BufLeave * setlocal statusline=%!mi#statusline#inactive()
    " set statusline=%!mi#statusline#horizontal_line()
    set statusline=─
    set fillchars+=stl:─,stlnc:─
    set laststatus=0

    autocmd Filetype * ++once
          \ execute 'colorscheme' mi#utils#pick_one(['slate', 'habamax', 'lunaperche', 'sorbet',
          \    'murphy', 'retrobox', 'wildcharm', 'zaibatsu'])
          \ | doautocmd ColorScheme
    autocmd Filetype typescript,typescriptreact,javascript,javascriptreact,markdown,json,jsonc
          \  let b:fmt_cmd = $'deno fmt --ext {expand("%:e")} -'
    autocmd Filetype sql let b:fmt_cmd = 'sql-formatter --config ~/dotfiles/.config/sql_formatter/sql_formatter.json'
    autocmd Filetype nix let b:fmt_cmd = 'alejandra -q -'
    autocmd Filetype gleam let b:fmt_cmd = 'gleam format --stdin'

    autocmd Filetype vim let b:symbol_list = [
          \ '..=', '=>', '<=', '==', '->',
          \ '==#', '==?', '!=#', '!=?', '>#', '>?', '>=#', '>=?',
          \ '<#', '<?', '<=#', '<=?', '=~#', '=~?', '!~#', '!~?',
          \]
    autocmd Filetype typescript,typescriptreact,javascript,javascriptreact let b:symbol_list = ['=>', '===']
    autocmd Filetype gleam let b:symbol_list = ['->', '|>', '<>', '<-', '_ ->']

    autocmd FileType * call mi#cmp#apply_syn_keywords_dict()
  endif

  " {{{ restore-cursor
  " :h restore-cursor
  autocmd BufReadPost *
        \ if line("'\"") > 1 && line("'\"") <= line("$") && &ft !~# 'commit'
        \ |   execute 'normal! g`"zz'
        \ | endif
  " }}}

  " {{{ ensure_dir
  " https://vim-jp.org/vim-users-jp/2011/02/20/Hack-202.html
  function! s:ensure_dir(dir, force)
    if !isdirectory(a:dir) && (a:force || confirm($'"{a:dir}" does not exist. Create?', "y\nN"))
      call mkdir(iconv(a:dir, &encoding, &termencoding), 'p')
    endif
  endfunction
  autocmd BufWritePre * call s:ensure_dir(expand('<afile>:p:h'), v:cmdbang)
  " }}}

  " {{{ validate_filename
  " https://zenn.dev/vim_jp/articles/f02adb4f325e51
  " :h interrupt
  function! s:validate_filename(filename, force) abort
    if a:force
      return
    endif
    const invalid_chars = '!&()[]{}<>^*=+:;''",`~?|'
    for char in split(invalid_chars, '\zs')
      if stridx(a:filename, char) >= 0
        echoerr 'Invalid filename:' a:filename
        call interrupt()
      endif
    endfor

    const valid_extension = '\.\?[[:alnum:]]\+$'
    if a:filename !~ valid_extension
      echoerr 'Invalid filename:' a:filename
      call interrupt()
    endif
  endfunction
  autocmd BufWritePre * call s:validate_filename(expand('<afile>:p'), v:cmdbang)
  " }}}

  " {{{ hist_clean
  " https://blog.atusy.net/2023/07/24/vim-clean-history/
  function! s:hist_clean() abort
    const cmd = histget(':', -1)
    if cmd =~# '^e\%[dit]!\?$' || cmd =~# '^enew\?$' || cmd =~# '^[wx]\?q\?a\?!\?$'
      call histdel(':', -1)
    endif
  endfunction
  autocmd ModeChanged c:* call s:hist_clean()
  " }}}

  " https://zenn.dev/kawarimidoll/articles/33cf46fae69809
  function! s:check_prefix_match_files() abort
    let fname = expand('<afile>')
    let dname = fnamemodify(fname, ':h')
    let files_in_dir = split(glob(dname == '.' ? '*' : $'{dname}/*'), "\n")
    let possible = filter(copy(files_in_dir), $'v:val =~# "^\\V{fname}"')
    if empty(possible)
      return
    endif
    let choice = confirm($'"{fname}" does not exist. Do you want to open file below?',
          \ possible->copy()->map({k, v -> $'&{k+1} {v}'})->join("\n"), 0)
    if choice == 0
      return
    endif
    call timer_start(0, {->execute($'edit {possible[choice - 1]} | bwipeout! #')})
  endfunction
  autocmd BufNewFile * call s:check_prefix_match_files()

augroup END

call timer_start(100, {->execute('source ~/dotfiles/.vim/ready.vim')})

if !has('nvim')
  " ref: https://zenn.dev/vim_jp/articles/20240304_ekiden_disable_plugin
  let s:save_rtp = &runtimepath
  set runtimepath-=$VIMRUNTIME
  autocmd SourcePre */plugin/* ++once let &runtimepath = s:save_rtp
endif