" vim: shiftwidth=2
vim9script noclear

# Encoding
if !v:vim_did_enter
  set encoding=utf-8
  scriptencoding utf-8
  $MYVIMRC = resolve(expand('<sfile>'))
endif

# Local utility functions
export def SIDPrefix(): string
  # Returns "<SNR>{script-ID}_"
  return expand('<SID>')
enddef
def SNR(): string
  return matchstr(SIDPrefix(), '<SNR>\zs\d\+\ze_$')
enddef

def Rc(fname: string): string
  return Filesystem.rcfile_prefix .. fname
enddef

export def JoinPath(...arg: list<any>): string
  return join(arg, Filesystem.slash)
enddef

export def Has(object: any, to_search: any): bool
  var obj_type = type(object)
  if obj_type == v:t_string
    return HasInString(object, to_search)
  elseif obj_type == v:t_list
    return HasInList(object, to_search)
  elseif obj_type == v:t_dict
    return HasInDict(object, to_search)
  endif
  return false
enddef

export def HasInString(object: string, to_search: string): bool
  return stridx(object, to_search) != -1
enddef

export def HasInList(object: list<any>, to_search: any): bool
  return index(object, to_search) != -1
enddef

export def HasInDict(object: dict<any>, to_search: string): bool
  return has_key(object, to_search)
enddef

def GetMsgString(msg: string): string
  return '[vimrc] ' .. msg
enddef

def EchomsgWithColor(msg: string, hl_group: string): void
  execute 'echohl' hl_group
  echomsg GetMsgString(msg)
  echohl NONE
enddef

export def Echomsg(msg: string): void
  call EchomsgWithColor(msg, 'NONE')
enddef

export def EchomsgError(msg: string): void
  call EchomsgWithColor(msg, 'Error')
enddef

export def EchomsgWarning(msg: string): void
  call EchomsgWithColor(msg, 'WarningMsg')
enddef

export def Echo(msg: string): void
  echo GetMsgString(msg)
enddef

export def EchoQuestion(question: string): void
  echon GetMsgString(question) .. ' '
  echohl Question
  echon '[Y/N]'
  echohl NONE
  echon "\n"
enddef

export def Glob(expr: string, nosuf: bool = false, alllinks: bool = false): list<string>
  return glob(expr, nosuf, true, alllinks)
enddef

export def GetcharString(...expr: list<number>): any
  return nr2char(call('getchar', expr))
enddef

export def Ask(question: string): bool
  EchoQuestion(question)
  return GetcharString() =~? 'y'
enddef

export def Mkdir(dir: string, ...opt: list<any>): bool
  if isdirectory(dir)
    return true
  endif
  if !exists('*mkdir')
    EchomsgError('built-in mkdir() not found.')
    return false
  endif
  return call('mkdir', [dir] + opt) ? true : false
enddef

export def Input(prompt: string, ...opt: list<any>): string
  try
    return call('input', [prompt] + opt)
  catch /\C^Vim:Interrupt$/
    return ''
  endtry
  return ''
enddef

# Global utility functions
legacy def! VimrcFunc(funcname: string): func
  return function(SIDPrefix() .. funcname)
enddef

export def VimrcVar(varname: string): any
  return eval(varname)
enddef

export def SetUndoFtplugin(config: string)
  var restorer = 'execute ' .. string(config)
  if exists('b:undo_ftplugin')
    b:undo_ftplugin = restorer .. '|' .. b:undo_ftplugin
  else
    setbufvar('%', 'undo_ftplugin', restorer)
  endif
enddef
command! -nargs=1 -complete=command SetUndoFtplugin
      \ call SetUndoFtplugin(<q-args>)

final IsWindows = has('win32')
final IsLinux = has('linux')
final IsMac = has('mac')
final IsUnix = IsLinux || IsMac
final IsWSL = IsLinux && system('uname -r') =~? 'microsoft'

export var Filesystem: dict<string>
if IsWindows
  $DOT_VIM = expand('~\vimfiles')
  Filesystem = {
    slash: '\',
    path_separator: ';',
    rcfile_prefix: '_'
  }
else
  $DOT_VIM = expand('~/.vim')
  Filesystem = {
    slash: '/',
    path_separator: ':',
    rcfile_prefix: '.'
  }
endif

# Startup config
if !v:vim_did_enter
  if has('multi_lang') && has('menu')
    set langmenu=ja.utf-8
  endif

  if &term =~# '\<256color\>' && expand('$TERM_PROGRAM') !=# 'Apple_Terminal'
    set termguicolors  # Use true color if possible.
  endif
  if &term =~# '\<xterm\>'
    &t_EI = "\<ESC>[2 q"  # Use Block style cursor in Normal-mode.
    &t_SI = "\<ESC>[6 q"  # Use Bar style cursor in Insert-mode.
    &t_SR = "\<ESC>[4 q"  # Use underline style cursor in Replace-mode.
    set mouse=a
  endif

  if expand('$TERM_PROGRAM') ==# 'WezTerm'
    &t_Cs = "\e[4:3m"
    &t_Ce = "\e[4:0m"
  endif

  # disable default plugins
  g:loaded_2html_plugin = 1
  g:loaded_getscriptPlugin = 1
  g:loaded_gzip = 1
  g:loaded_zipPlugin = 1
  g:loaded_tarPlugin = 1
  g:loaded_vimballPlugin = 1
  g:loaded_netrwPlugin = 1

  &runtimepath ..= ',' .. escape(JoinPath($DOT_VIM, 'runtime'), ' \')

  # Set environmental variables on gVim.
  {
    var envrc_ = JoinPath(expand('~'), Rc('envrc'))
    if has('gui_running') && filereadable(envrc_)
      var lines = readfile(envrc_)
            ->map((_, val): string => substitute(val, '\v%(\_^|\s)#.*$', '', 'g'))
            ->filter((_, val): bool => !empty(val))

      for line in lines
        var name: string
        var value: string
        [name, value] = line->split('=')
        execute '$' .. name .. ' = ' .. value->expand()->string()
      endfor
    endif
  }
endif
# Initialize autocmd
(filename: string) => {
  var pattern = '^\s*aug\%[roup]\s\+\zs\S\+\ze\s*'
  var augroups = readfile(filename)
        ->filter((_, line): bool => stridx(line, 'aug') != -1)
        ->filter((_, line): bool => line =~# pattern)
        ->map((_, line): string => matchstr(line, pattern))
        ->filter((_, augroup): bool => augroup !=? 'END')
        ->sort()
        ->uniq()
  for augroup in augroups
    execute 'augroup ' .. augroup
      autocmd!
    execute 'augroup END'
  endfor
}(expand('<sfile>'))

# minpac
var PluginList: list<string>
var OptPluginList: list<string>
if &loadplugins
  PluginList = Glob(JoinPath($DOT_VIM, 'pack', 'minpac', 'start', '*'))
               ->filter((_, val): bool => isdirectory(val))
               ->map((_, val): string => fnamemodify(val, ':p:h:t'))
  OptPluginList = Glob(JoinPath($DOT_VIM, 'pack', 'minpac', 'opt', '*'))
               ->filter((_, val): bool => isdirectory(val))
               ->map((_, val): string => fnamemodify(val, ':p:h:t'))
  def PluginExists(plugin: string): bool
    return HasInList(PluginList, plugin)
  enddef

  def OptPluginExists(plugin: string): bool
    return HasInList(OptPluginList, plugin)
  enddef
else
  def PluginExists(plugin: string): bool
    return false
  enddef

  def OptPluginExists(plugin: string): bool
    return false
  enddef
endif
def PackRegister()
  minpac#add('k-takata/minpac', {type: 'opt'})

  minpac#add('itchyny/vim-cursorword')
  minpac#add('kamichidu/vim-textobj-function-go')
  minpac#add('kana/vim-altr')
  minpac#add('kana/vim-operator-user')
  minpac#add('kana/vim-textobj-user')
  minpac#add('lambdalisue/gina.vim')
  minpac#add('lambdalisue/vim-findent')
  minpac#add('lambdalisue/vim-gista')
  minpac#add('mityu/vim-brownie')
  minpac#add('mityu/vim-gram')
  minpac#add('mityu/vim-gram-sources')
  minpac#add('mityu/vim-vim9context')
  minpac#add('mityu/vim-wispath')
  minpac#add('osyo-manga/vim-jplus')
  minpac#add('previm/previm')
  minpac#add('skanehira/gh.vim')
  minpac#add('thinca/vim-ambicmd')
  minpac#add('thinca/vim-ft-help_fold')
  minpac#add('thinca/vim-partedit')
  minpac#add('thinca/vim-prettyprint')
  minpac#add('thinca/vim-qfreplace')
  minpac#add('thinca/vim-quickrun')
  minpac#add('thinca/vim-themis')
  minpac#add('tyru/capture.vim')
  # minpac#add('tyru/eskk.vim')
  minpac#add('tyru/open-browser.vim')
  minpac#add('vim-jp/autofmt')
  minpac#add('vim-jp/vimdoc-ja')
  minpac#add('vim-jp/vital.vim')
  minpac#add('vim-scripts/autodate.vim')

  minpac#add('lambdalisue/suda.vim', {type: 'opt'})
  minpac#add('mattn/vim-lsp-settings', {type: 'opt'})
  minpac#add('prabirshrestha/asyncomplete-lsp.vim', {type: 'opt'})
  minpac#add('prabirshrestha/asyncomplete.vim', {type: 'opt'})
  minpac#add('prabirshrestha/vim-lsp', {type: 'opt'})
  minpac#add('tweekmonster/helpful.vim', {type: 'opt'})
  minpac#add('y0za/vim-reading-vimrc', {type: 'opt'})

  # Operator-user plugins
  minpac#add('osyo-manga/vim-operator-swap', {type: 'opt'})
  minpac#add('kana/vim-operator-replace', {type: 'opt'})
  minpac#add('rhysd/vim-operator-surround', {type: 'opt'})
  # minpac#add('tyru/caw.vim', {type: 'opt'})
  minpac#add('mityu/caw.vim', {type: 'opt', branch: 'support-vim9script-tmp'})  # tmp
  minpac#add('sgur/vim-operator-openbrowser', {type: 'opt'})

  # Textobj-user plugins
  minpac#add('kana/vim-textobj-entire', {type: 'opt'})
  minpac#add('kana/vim-textobj-function', {type: 'opt'})
  minpac#add('kana/vim-textobj-indent', {type: 'opt'})
  minpac#add('kana/vim-textobj-line', {type: 'opt'})
  minpac#add('mityu/vim-textobj-commentblock', {type: 'opt'})
  minpac#add('thinca/vim-textobj-between', {type: 'opt'})
enddef
def PackInit(): bool
  silent! packadd minpac
  silent! minpac#init()
  if !exists('*minpac#init()')
    # Download minpac...
    var minpac_path = JoinPath($DOT_VIM, 'pack', 'minpac', 'opt', 'minpac')
    Echomsg('Downloading minpac...')
    system('git clone https://github.com/k-takata/minpac ' .. minpac_path)

    silent! packadd minpac
    silent! minpac#init()
    if !exists('*minpac#init()')
      return false
    endif
  endif

  PackRegister()

  return true
enddef
def PackUpdate()
  if PackInit()
    minpac#update()
  endif
enddef
def PackClean()
  if PackInit()
    minpac#clean()
  endif
enddef
def PackStatus()
  if exists('*minpac#status')
    minpac#status()
  else
    EchomsgError('minpac isn''t loaded yet.')
  endif
enddef
command! -bar PackInit   call PackInit()
command! -bar PackUpdate call PackUpdate()
command! -bar PackClean  call PackClean()
command! -bar PackStatus call PackStatus()

# Options {{{
if !v:vim_did_enter
  syntax on
  filetype plugin indent on
endif
if !exists('g:colors_name')  # Don't override colorscheme
  colorscheme domusaurea
endif
language C
set relativenumber number
set wrap
set smartindent autoindent
set cinoptions=:0,g0,N-s,E-s
set backspace=eol,start,indent
set pumheight=10
set completeopt=menuone,noselect
set noequalalways
set scrolloff=1
set colorcolumn=78
set tabstop=4
set shiftwidth=4
set expandtab
set smarttab
set softtabstop=4
set hlsearch
set display=lastline
set listchars=tab:\|-
set autoread
set incsearch ignorecase
set showmatch matchtime=1
set cursorline cursorlineopt=number
set laststatus=2
set showtabline=2
set cmdheight=2 cmdwinheight=10
set wildmenu
set wildignore& wildignore+=*.DS_STORE
set history=500
set keywordprg=:help
set shortmess& shortmess+=Ic
set helplang=ja
set hidden
set diffopt=internal,algorithm:histogram
#set breakindent
#set virtualedit& virtualedit+=block
set virtualedit=block
set complete=.,i,d,w,b,u
set noimdisable
set lazyredraw
set previewpopup=highlight:Normal
set termwinkey=<C-w>
set noesckeys
set nowrapscan
set timeoutlen=3000 ttimeoutlen=100
# }}}

export final CacheDir = JoinPath(expand('~'), '.cache', 'vimrc')
Mkdir(CacheDir, 'p')

{
  final undodir_ = JoinPath(CacheDir, 'undodir')
  if Mkdir(undodir_, 'p')
    set undofile
    &undodir = undodir_
  else
    set noundofile
  endif

  final directory_ = JoinPath(CacheDir, 'swapfile')
  if Mkdir(directory_, 'p')
    set swapfile
    &directory = JoinPath(fnamemodify(directory_, ':p'), '')
  else
    set noswapfile
  endif
  set nobackup nowritebackup
}

if has('kaoriya')
  set fileencodings=guess,utf-8
  set ambiwidth=auto
else
  set fileencodings=utf-8,euc-jp,cp932,sjis
  set ambiwidth=double
endif
if IsUnix
  set path& path+=/usr/local/include
endif
if executable('rg')
  &grepprg = 'rg --vimgrep'
  &grepformat = '%f:%l:%c:%m'
elseif executable('ag')
  &grepprg = 'ag --vimgrep'
  &grepformat = '%f:%l:%c:%m'
endif
augroup vimrc_filetype
  autocmd FileType c,cpp setlocal foldmethod=indent
  autocmd BufRead .envrc set filetype=sh
  autocmd FileType makefile setlocal noexpandtab nosmarttab
augroup END
augroup vimrc_mru
  autocmd BufRead,BufWritePost * call vimrc#mru#onReadFile()
augroup END
augroup vimrc_checktime
  autocmd CursorHold * if getcmdwintype() ==# '' | checktime | endif
augroup END
if executable('chmod')
  # This hack is from github.com/thinca/config/dotfiles/dot.vim/vimrc. Thanks!
  augroup vimrc_autoexecutable
    autocmd BufWritePost * AddPermissionX()
  augroup END

  def AddPermissionX()
    var file = expand('%:p')
    if stridx(getline(1), '#!') == 0 && !executable(file)
      silent! call system('chmod a+x ' .. shellescape(file))
    endif
  enddef
endif
augroup vimrc_quickfix_window
  autocmd QuickfixCmdPost [^l]* cwindow
  autocmd QuickfixCmdPost l* lwindow
augroup END

# Mapping {{{
nnoremap : q:A
vnoremap : q:A
nnoremap <Space>: q:k
vnoremap <Space>: q:k
nnoremap / q/A
vnoremap / q/A
nnoremap <Space>/ q/k
vnoremap <Space>/ q/k
nnoremap ? q?A
vnoremap ? q?A
nnoremap <Space>? q?k
vnoremap <Space>? q?k
nnoremap <Space>; :
vnoremap <Space>; :

noremap - <C-x>
noremap + <C-a>
noremap ( [(
noremap ) ])
noremap j gj
noremap k gk
noremap gj j
noremap gk k

nnoremap <CR> o<ESC>
nnoremap Y y$
nnoremap <C-h> <Cmd>nohlsearch<CR>
nnoremap <C-w>s <Cmd>belowright wincmd s<CR>
nnoremap <C-w><C-s> <Cmd>belowright wincmd s<CR>
nnoremap <C-w>v <Cmd>belowright wincmd v<CR>
nnoremap <C-w><C-v> <Cmd>belowright wincmd v<CR>
nnoremap <C-w>c <Cmd>belowright copen<CR>
nnoremap <C-w>t <Cmd>tabnew<CR>
nnoremap <C-w><C-t> <Cmd>tabnew<CR>
nnoremap <Space>th <Cmd>vertical terminal ++close<CR>
nnoremap <Space>tj <Cmd>belowright terminal ++close<CR>
nnoremap <Space>tk <Cmd>terminal ++close<CR>
nnoremap <Space>tl <Cmd>belowright vertical terminal ++close<CR>
nnoremap <Space>tt <Cmd>tab terminal ++close<CR>
nnoremap <Space>w <Cmd>update<CR>
nnoremap <Space>q <C-w>q
nnoremap ZZ <Nop>
nnoremap ZQ <Nop>
nnoremap Q <Nop>
nnoremap <C-k> 7gk
nnoremap <C-j> 7gj
# Avoid textobj-function calls appearing on command history
nnoremap <silent> . .
nnoremap * *N

nnoremap m <Nop>
vnoremap m <Nop>

inoremap <C-m> <C-g>u<C-m>
inoremap <C-u> <C-g>u<C-u>
inoremap <C-j> <C-g>u<C-j>
inoremap <C-l> <C-x>
inoremap <C-l><C-n> <C-x><C-n><C-n>
inoremap <C-l><C-p> <C-x><C-p><C-p>
inoremap <silent> <C-j> <C-r>=vimrc#pinsnip#expand()<CR>

vnoremap g+ g<C-a>
vnoremap g- g<C-x>
vnoremap <C-k> 7gk
vnoremap <C-j> 7gj
vnoremap * <ESC>*Ngvne<Cmd>nohlsearch<CR>
vnoremap g* <ESC>g*Ngvne<Cmd>nohlsearch<CR>
vnoremap n n<Cmd>nohlsearch<CR>
vnoremap N N<Cmd>nohlsearch<CR>

cnoremap <C-l> <C-f>
cnoremap <C-f> <Right>
cnoremap <C-p> <Up>
cnoremap <C-n> <Down>
cnoremap <C-b> <Left>
cnoremap <C-a> <C-b>
cnoremap <C-[> <C-c>

nnoremap <C-w>, <Cmd>call <SID>ToggleQuickfix()<CR>
def ToggleQuickfix()
  var wincount = winnr('$')
  try
    cclose
    if wincount == winnr('$')
      cwindow
      if wincount == winnr('$')
        EchomsgError('No quickfix window')
      endif
    endif
  catch
    echo v:exception
  endtry
enddef

nnoremap <C-w>. <Cmd>call <SID>ToggleLocationlist()<CR>
def ToggleLocationlist()
  var wincount = winnr('$')
  try
    lclose
    if wincount == winnr('$')
      lwindow
    endif
  catch /^Vim(\S\{-})\:E776\:/
    echohl ErrorMsg
    echomsg matchstr(v:exception, '^Vim(\S\{-})\:\zs.*$')
    echohl NONE
  catch
    echoerr v:exception
  endtry
enddef
# }}}
def TerminalMap(mapcmd: string, lhs: string, rhs: string)
  execute printf('%s %s%s %s%s',
        mapcmd, &termwinkey, lhs, &termwinkey, rhs)
enddef
TerminalMap('tnoremap', ':', ':<C-f>A')
TerminalMap('tnoremap', '<Space>:', ':<C-f>k')
TerminalMap('tnoremap', '<ESC>', 'N')
TerminalMap('tnoremap', 'p', '""')
TerminalMap('tnoremap', '<C-r>', '"')
TerminalMap('tnoremap', 't', '<Cmd>tabnew<CR>')
TerminalMap('tnoremap', 'n', '<Cmd>botright new<CR>')

# Loop
def LoopDefine(config: dict<any>)
  var prefix = printf('<Plug>(<SID>-loop-%s)', config.id)
  var enter_with = config.enter_with
  var mode = get(config, 'mode', 'n')
  var plug_map: dict<string>
  for key in ['prefix', 'main', 'do']
    plug_map[key] = printf('%s(%s)', prefix, key)
  endfor

  execute printf('%snoremap %s <Nop>', mode, plug_map.prefix)
  for mapping in config.map
    var lhs: string
    var rhs: string
    var commands: list<list<string>>
    [lhs, rhs] =
        mapping->map((_, val): string => substitute(val, '|', '<bar>', 'g'))
    commands->add([mode .. 'noremap', plug_map.do .. lhs, rhs])
            ->add([mode .. 'map', enter_with .. lhs, plug_map.main .. lhs])
            ->add([mode .. 'map', plug_map.main .. lhs, plug_map.do .. lhs .. plug_map.prefix])
            ->add([mode .. 'map', plug_map.prefix .. lhs, plug_map.main .. lhs])
    execute commands->mapnew('join(v:val)')->join("\n")
  endfor

  if mode ==# 'n'
    var t_config = deepcopy(config)
    t_config.mode = 't'
    if t_config.enter_with[: strlen(&termwinkey)] !=# &termwinkey &&
        t_config.enter_with[0] !=# printf('"\%s"', &termwinkey)->eval()
      t_config.enter_with = &termwinkey .. config.enter_with
    endif
    t_config.map->map('LoopDefineGenerateTmap(v:val)')
    LoopDefine(t_config)
  endif
enddef

def LoopDefineGenerateTmap(value: list<string>): list<string>
  var rhs = value[1]
        ->substitute('\c<SID>', SIDPrefix(), 'g')
        ->substitute('\zs<\ze.\{-}>', '\\<lt>', 'g')
  rhs = '<Cmd>execute "normal! ' .. rhs .. '"<CR>'
  return [value[0], rhs]
enddef

def SimpleLoopDefine(config: dict<any>)
  var new_config = deepcopy(config)
  new_config.map = config.follow_key->split('\zs')
        ->mapnew((_, val): list<string> => [val, config.enter_with .. val])
  LoopDefine(new_config)
enddef

# Window management
SimpleLoopDefine({
  id: 'Window',
  enter_with: '<C-w>',
  follow_key: 'hjklHJKLq<>-+_|=',
})
LoopDefine({
  id: 'Tab',
  enter_with: 'g',
  map: [
    ['h', 'gT'],
    ['l', 'gt'],
    ['T', '<Cmd>call <SID>MapTabmove(-1)<CR>'],
    ['t', '<Cmd>call <SID>MapTabmove(1)<CR>'],
  ]
})

def MapTabmove(delta: number)
  var tab_count = tabpagenr('$')
  if tab_count == 1
    return
  endif
  var current = tabpagenr() - 1
  var move_to = current + delta
  if move_to < 0
    while move_to < 0
      move_to += tab_count
    endwhile
  endif
  if move_to >= tab_count
    move_to = move_to % tab_count
  endif
  var movement = move_to - current
  var movecmd = 'tabmove '
  if movement < 0
    movecmd ..= string(movement)
  else
    movecmd ..= '+' .. string(movement)
  endif
  execute movecmd
enddef

# f F t T ; , declarations {{{
map <expr> f vimrc#charjump#jump(v:true, v:false)
map <expr> F vimrc#charjump#jump(v:false, v:false)
map <expr> t vimrc#charjump#jump(v:true, v:true)
map <expr> T vimrc#charjump#jump(v:false, v:true)
noremap ; <Cmd>call vimrc#charjump#repeat(v:false)<CR>
noremap , <Cmd>call vimrc#charjump#repeat(v:true)<CR>
# }}}

def CmdwinEnter()
  # Type <CR> to execute current line in command-line window.
  # This mapping is overwritten when ambicmd.vim is installed.
  nnoremap <buffer> <CR> <CR>

  # Return back to the current window from command-line window with
  # inputting <C-c> once.
  nnoremap <buffer> <C-c> <C-c><C-c>
  nmap <buffer> q <C-c>
  imap <buffer> <C-c> <ESC><C-c>

  inoremap <buffer> <C-l><C-n> <C-x><C-n>
  inoremap <buffer> <C-l><C-p> <C-x><C-n>

  # Make executing a previous command easier.
  nnoremap <buffer> / <Cmd>call <SID>CmdwinSelectHistory()<CR>
  vimrc#incsearch#setup()

  final cmdwin_type = expand('<afile>')
  if cmdwin_type ==# ':'
    inoremap <expr> <buffer> <C-p> <SID>CmdwinCompletion(0)
    inoremap <expr> <buffer> <C-n> <SID>CmdwinCompletion(1)
    b:completeopt_save = &completeopt
    setlocal completeopt=menu,preview
    execute printf(':silent! :1,$-%d delete _', &cmdwinheight)
    normal! G
    if exists('g:asyncomplete_loaded') && g:asyncomplete_loaded
      asyncomplete#disable_for_buffer()
    endif
  endif
enddef
def CmdwinLeave()
  if exists('b:completeopt_save')
    &completeopt = b:completeopt_save
  endif
enddef
def CmdwinCompletion(select_next: bool): string
  if pumvisible()
    return select_next ? "\<C-n>" : "\<C-p>"
  else
    return "\<C-x>\<C-v>"
  endif
enddef
def CmdwinSelectHistory()
  var history = split(execute('history ' .. getcmdwintype()), "\n")[1 :]
  map(history, (_, val) => matchstr(val, '^>\?\s*\d\+\s\+\zs.\+$'))
  reverse(history)
  gram#select({
         name: getcmdwintype() .. 'history',
         items: history,
         callback: SIDPrefix() .. 'CmdwinSelectHistoryCallback',
       })
enddef
def CmdwinSelectHistoryCallback(item: dict<string>)
  if getline('$') ==# ''
    setline('$', item.word)
  else
    append('$', item.word)
  endif
  noautocmd normal! G$
enddef

augroup vimrc_mapping
  autocmd CmdWinEnter * call CmdwinEnter()
  autocmd CmdWinLeave * call CmdwinLeave()
augroup END

if IsMac
  g:mapleader = '_'
endif

# Mapping for commands
{
  var map_ = []
  add(map_, ['nnoremap', 'ev', 'edit $MYVIMRC'])
  add(map_, ['nnoremap', 'sv', 'source $MYVIMRC'])
  add(map_, ['nnoremap', 'k', 'call vimrc#mru#start()'])
  add(map_, ['nnoremap', 'b', 'call gram#sources#buffers#launch()'])
  add(map_, ['nnoremap', 'j', 'call <SID>GofForMapping()'])
  add(map_, ['nnoremap', 'f', 'call vimrc#filore#start()'])
  execute map_->mapnew((_, val): string => [val[0], '<Space>' .. val[1], printf('<Cmd>%s<CR>', val[2])]->join())->join("\n")
}

# Abbreviates
inoreabbrev todo: TODO:
inoreabbrev fixme: FIXME:
inoreabbrev xxx: XXX:
inoreabbrev note: NOTE:

# Commands
# Declarations
command! -bar CdCurrent cd %:p:h
command! -bar LcdCurrent lcd %:p:h
command! -bar TcdCurrent tcd %:p:h
command! -nargs=1 -complete=file Rename file <args>|call delete(expand('#'))
command! -nargs=? CopyToClipboard call setreg('+', getreg(<q-args>, 1))
command! -bar -nargs=? ClipBuffer call vimrc#clipbuffer(<q-args>)
command! ClearMessage execute repeat("echom ''\n", 201)
command! Helptags helptags ALL
command! -bang -nargs=+ -complete=command Filter call Filter(<bang>0, <f-args>)
command! -bar Draft call Draft()
command! -bar -nargs=+ -complete=command Vimrc <args> $MYVIMRC
command! -bar -nargs=* -complete=file ListTasks call vimrc#list_tasks(<q-args>)
command! DeleteUndoFiles call vimrc#delete_undofiles()
command! -nargs=? -complete=dir Gof call Gof(<q-args>)
command! -bar SwapBackslashAndBar call SwapBackslashAndBar()
command! -bar -nargs=? -complete=dir GitInitRepo call vimrc#git_init_repo(<q-args>)
command! -bar LocalPackUpdate call vimrc#update_local_packages()

# Thanks to cohama
command! EditAsUtf8 edit ++enc=utf-8 %
command! EditAsCp932 edit ++enc=cp932 %
command! EditAsUnix edit ++ff=unix %
command! EditAsDos edit ++ff=dos %
command! WriteAsUtf8 set fenc=utf-8|w

# Plugin Shortcuts
command! -bar -nargs=* MruEditHistoryStart call vimrc#mru#edit_history_start(<q-args>)
command! -bar MruDeleteUnexistHistory call vimrc#mru#delete_unexist_file_history()
command! -bar -nargs=? -complete=dir Filore call vimrc#filore#start(<q-args>)
command! -bar -bang -nargs=? -complete=dir Ls call vimrc#shcmd#ls(<bang>0, <f-args>)
command! -bar -bang -nargs=+ -complete=dir Mkdir call vimrc#shcmd#mkdir(<bang>0, <f-args>)
command! -bar -nargs=+ -complete=dir Touch call vimrc#shcmd#touch(<f-args>)
command! -bar -nargs=+ -complete=dir CpFile call vimrc#shcmd#cpfile(<f-args>)
command! -bar -bang -nargs=+ -complete=file Rm call vimrc#shcmd#rm(<bang>0, <f-args>)

def Filter(bang: bool, pat: string, ...cmd: list<string>)
  var output = execute(join(cmd, ' '))
                ->split("\n")
                ->filter((_: number, val: string): bool => val =~? pat)
                ->join("\n")
  if bang
    echomsg output
  else
    echo output
  endif
enddef

def Draft()
  setlocal buftype=nofile noswapfile
enddef

def FindGitroot(arg_target: string = bufname('%')): string
  var target: string = resolve(arg_target)
  var git_dir: string = finddir('.git', target .. ';')
  if git_dir ==# ''
    EchomsgWarning('Not a git repository: ' .. target)
    return ''
  endif
  return fnamemodify(git_dir, ':p:h:h')
enddef

def Gof(path: string)
  if !executable('gof')
    echo '"gof" command not found.'
    return
  endif
  var gofcmd = 'gof -f -tf "Tapi_gof"'
  if !empty(path)
    gofcmd ..= ' -d ' .. path
  endif
  var minwidth = min([&columns, 100])
  var minheight = min([&lines, 40])
  popup_create(term_start(gofcmd, {hidden: 1, term_finish: 'close'}),
        \ {minwidth: minwidth, minheight: minheight})
enddef

def GofForMapping()
  var git_root = FindGitroot()
  Gof(git_root ==# '' ? getcwd(winnr()) : git_root)
enddef

legacy def! Tapi_gof(bufnum: number, file_info: dict<string>)  # Be global
  var winid = win_getid(winnr('#'))
  var buftype = getwinvar(winid, '&buftype')
  var open_cmd = 'edit'
  if !(buftype ==# 'nofile' || buftype ==# '')
    open_cmd = 'split'
  endif
  win_execute(winid, open_cmd .. ' ' .. fnameescape(file_info.fullpath))
enddef

def SwapBackslashAndBar()
  if maparg('\', 'i') ==# "\<Nop>"
    inoremap \ _
    inoremap _ \
  else
    iunmap \
    iunmap _
  endif
enddef

# Installed plugins
if PluginExists('vim-cursorword')
  g:cursorword_highlight = 0
  augroup vimrc_cursorword
    autocmd ColorScheme * highlight CursorWord0 gui=underline cterm=underline term=underline guibg=NONE ctermbg=NONE
    autocmd ColorScheme * highlight CursorWord1 gui=underline cterm=underline term=underline guibg=NONE ctermbg=NONE
  augroup END
    highlight CursorWord0 gui=underline cterm=underline term=underline guibg=NONE ctermbg=NONE
    highlight CursorWord1 gui=underline cterm=underline term=underline guibg=NONE ctermbg=NONE
endif
if PluginExists('vim-altr')
  command! -bar AlterForward call altr#forward()
  command! -bar AlterBack call altr#back()
endif
if PluginExists('gina.vim')
  g:gina#action#mark_sign_text = '*'
  g:gina#core#console#enable_message_history = 1

  def GinaConfig()
    def Gina_nnoremap(scheme: string, lhs: string, rhs: string)
      gina#custom#mapping#nmap(scheme, lhs, rhs, {noremap: 1, silent: 1})

      # Set mapping manually for the first time.
      # TODO: More smart solution
      if HasInString(&filetype, scheme)
        execute 'nnoremap <buffer> <silent>' lhs rhs
      endif
    enddef

    Gina_nnoremap('status', '<C-]>', '<Cmd>Gina commit<CR>')
    Gina_nnoremap('commit', '<C-]>', '<Cmd>Gina status<CR>')
    Gina_nnoremap('status', '<Space>g', '<Cmd>Gina commit<CR>')
    Gina_nnoremap('commit', '<Space>g', '<Cmd>Gina status<CR>')
    Gina_nnoremap('status', '<C-l>', '<Cmd>Gina status<CR>')
    Gina_nnoremap('log', '<C-l>', '<Cmd>Gina log<CR>')
    Gina_nnoremap('branch', 'N',
            '<Cmd>call gina#action#call("branch:new")<CR>')
    Gina_nnoremap('/\v%(status|branch|log)', 'q', '<Cmd>close<CR>')

    # Jump between unadded files with n/p ..
    var rhs_base = printf(
            '<Cmd>call %sGinaStatusSelectAnother(v:count1, %%s)<CR>',
            SIDPrefix())
    Gina_nnoremap('status', 'n', printf(rhs_base, 'v:false'))
    Gina_nnoremap('status', 'N', printf(rhs_base, 'v:true'))
    # NOTE: Using v:true and v:false as variables above doesn't work because
    # it seems to be changed into 'false' and 'true' (without v: namespace).

    Gina_nnoremap('status', 'dd',
           printf('<Cmd>call %sGinaDiff()<CR>', SIDPrefix()))

    gina#custom#action#alias('branch', 'merge', 'commit:merge:no-ff')
    gina#custom#action#alias('branch', 'merge-ff', 'commit:merge:ff')

    gina#custom#command#option('\v%(^<%(cd|lcd|qrep)>)@<!$', '--opener=edit')
  enddef

  def GinaStatusSelectAnother(repeat_count: number, search_previous: bool)
    var flags = 'w'
    flags ..= search_previous ? 'b' : ''
    for i in range(1, repeat_count)
      search('\e[31.\+\e[m', flags)
    endfor
  enddef

  def GinaDiff()
    var filename = matchstr(getline('.'), '\e[31m\zs.\+\ze\e[m')
    filename = matchstr(filename, '\%(\_^modified\:\s\+\)\?\zs.\+')
    if filename ==# ''
      return
    endif
    var gitroot = fnamemodify(gina#core#get().repository, ':h')
    var cmd = 'git -C ' .. gitroot ..
            ' --no-pager diff --no-color ' .. filename
    var bufnr = term_start(cmd, {
            term_name: '[gina diff] ' .. filename,
            norestore: 1,
            })
    if bufnr != 0
      setlocal nocursorline nocursorcolumn filetype=diff
      nnoremap <buffer> <nowait> q <C-w>q
      cursor(1, 0)
    endif
  enddef

  augroup vimrc_gina
    autocmd Filetype gina-* ++once GinaConfig()
  augroup END
  nnoremap <Space>g <Cmd>Gina status<CR>
endif
if PluginExists('vim-findent')
  augroup vimrc_findent
    autocmd FileType * if &l:modifiable | execute 'Findent' | endif
  augroup END
endif
if PluginExists('vim-brownie')
  g:brownie_template_dirs =
        [JoinPath(expand('$DOT_VIM'), 'runtime', 'extension', 'brownie')]
  g:brownie_extra_imports = {
    cpp: ['c'],
    vimspec: ['vim'],
  }

  def BrownieFiletype(): string
    return getbufvar(brownie#get_current_bufnr(), '&filetype')
  enddef
  def VimrcTemplateComplete(arg: string, line: string, pos: number): list<string>
    return filter(brownie#list(BrownieFiletype(), 'template'),
                (_, val) => stridx(val, arg) == 0)
  enddef

  command! -nargs=1 -complete=customlist,VimrcTemplateComplete Template
        \ call brownie#extract(BrownieFiletype(), 'template', <q-args>)
endif
if PluginExists('vim-gram')
  augroup vimrc_gram_init
    autocmd User gram-first-start ++once InitGramMapping()
  augroup END

  def InitGramMapping()
    gram#custom#map_action('n', '<CR>', 'select-item')
    gram#custom#map_action('n', 'q', 'quit')
    gram#custom#map_action('n', 'j', 'select-next-item')
    gram#custom#map_action('n', 'k', 'select-prev-item')
    gram#custom#map_action('n', 'i', 'start-insert')
    gram#custom#map_action('n', 'p', 'preview')
    gram#custom#map_action('i', '<ESC>', 'stop-insert')
    gram#custom#map_action('i', '<CR>', 'stop-insert')
    gram#custom#map_action('i', '<C-j>', 'stop-insert')
    gram#custom#map_action('i', '<C-h>', 'delete-char')
    gram#custom#map_action('i', '<C-w>', 'delete-word')
    gram#custom#map_action('i', '<C-b>', 'move-to-right')
    gram#custom#map_action('i', '<C-f>', 'move-to-left')
    gram#custom#map_action('i', '<C-a>', 'move-to-head')
    gram#custom#map_action('i', '<C-e>', 'move-to-tail')
    gram#custom#map_action('i', '<C-u>', 'delete-to-head')
    gram#custom#map_key('n', '<NL>', '<CR>')
    gram#custom#map_key('i', '<Del>', '<C-h>')
    gram#custom#map_key('i', '<BS>', '<C-h>')
  enddef
endif
if PluginExists('vim-wispath')
  imap <C-l><C-f> <Plug>(wispath-complete)
endif
if PluginExists('vim-jplus')
  map J <Plug>(jplus)
endif
if PluginExists('previm')
  g:previm_show_header = 0
  g:previm_enable_realtime = 1
  if IsWSL
    g:previm_open_cmd = '/mnt/c/Program Files/Google/Chrome/Application/chrome.exe'
  endif
endif
if PluginExists('vim-ambicmd')
  def AmbicmdExpand(key: string): string
    var expander = ambicmd#expand(key)
    return (expander ==# key ? '' : "\<C-g>u") .. expander
  enddef
  augroup vimrc_ambicmd
    autocmd CmdWinEnter : call SetupAmbicmdForCmdwin()
  augroup END
  cnoremap <expr> <Space> ambicmd#expand("\<Space>")
  cnoremap <expr> <CR> ambicmd#expand("\<CR>")
  cnoremap <expr> <bar> ambicmd#expand("\<bar>")

  def SetupAmbicmdForCmdwin()
    inoremap <buffer> <expr> <Space> <SID>AmbicmdExpand("\<Space>")
    inoremap <buffer> <expr> <bar> <SID>AmbicmdExpand("\<bar>")
    inoremap <buffer> <expr> <CR> <SID>AmbicmdExpand("\<CR>")
    inoremap <buffer> <expr> <C-j> <SID>AmbicmdExpand('')
  enddef

  g:ambicmd#show_completion_menu = 1

  def AmbicmdBuildRule(cmd: string): list<string>
    var rule = []
    rule += ['\c^' .. cmd .. '$']
    rule += ['\c^' .. cmd]

    for len in range(1, strlen(cmd))
      var prefix = strpart(cmd, 0, len)->toupper()->substitute('.\zs', '.\\{-}', 'g')
      var suffix = cmd[len :]
      var matcher = '\C^' .. prefix .. suffix
      rule += [matcher .. '$', matcher]
    endfor

    rule += ['\c' .. cmd]
    rule += ['.\\{-}' .. substitute(cmd, '.\zs', '.\\{-}', 'g')]
    return rule
  enddef
  g:ambicmd#build_rule = SIDPrefix() .. 'AmbicmdBuildRule'
endif
if PluginExists('vim-quickrun')
  g:quickrun_config = {}
  g:quickrun_config['_'] = {
    ['outputter']: 'multi',
    ['outputter/multi/targets']: ['buffer', 'error'],
    ['outputter/error/success']: 'buffer',
    ['outputter/error/error']: 'quickfix',
    ['outputter/buffer/close_on_empty']: 1,
    runner: 'job',
  }

  g:quickrun_config.cpp = {
    cmdopt: '-std=c++17'
  }
  g:quickrun_config['cpp/sfml'] = {
    type: 'cpp',
    cmdopt: '-std=c++17 -lsfml-audio -lsfml-graphics -lsfml-network -lsfml-system -lsfml-window',
  }
  g:quickrun_config.objc = {
    command: 'cc',
    execute: ['%c %s -o %s:p:r -framework Foundation', '%s:p:r %a', 'rm -f %s:p:r'],
    tempfile: '%{tempname()}.m',
  }
  g:quickrun_config.applescript = {
    command: 'osascript',
    execute: '%c %s:p',
    tempfile: '%{tempname()}.applescript',
  }
  g:quickrun_config.python = {
    command: 'python3',
    ['hook/eval/template']: 'print(%s)'
  }
  g:quickrun_config.python2 = {
    command: 'python',
    ['hook/eval/template']: 'print(%s)'
  }

  if IsWSL
    g:quickrun_config.dosbatch = {
      command: 'cmd.exe',
      exec: '%c /Q /c \$(wslpath -w %s) %a'
    }
  endif

  nnoremap <expr> <C-c> quickrun#is_running() ?
        \ '<Cmd>call quickrun#sweep_sessions()<CR>' :
        \ '<C-c>'
  nmap <Space>r <Plug>(quickrun)
  vmap <Space>r <Plug>(quickrun)
  augroup vimrc_filetype
    autocmd FileType quickrun nnoremap <buffer> q <C-w>q
  augroup END
endif
if PluginExists('capture.vim')
  augroup vimrc_filetype
    autocmd Filetype capture nnoremap <buffer> q <C-w>q
  augroup END
endif
if PluginExists('autofmt')
  set formatexpr=autofmt#japanese#formatexpr()
endif
if OptPluginExists('asyncomplete.vim')
  def AsyncompletePreprocessor(context: dict<any>, matches_dict: dict<any>)
    var base = tolower(context.base)
    var completions: list<dict<any>>
    var subcompletions: list<dict<any>>
    var fuzzycompletions: list<dict<any>>

    if trim(context.base) !=# ''
      for matches in values(matches_dict)
        for candidate in matches.items
          var idx = stridx(tolower(candidate.word), base)
          if idx == -1
            continue
          elseif idx == 0
            add(completions, candidate)
          else
            add(subcompletions, candidate)
          endif
        endfor
      endfor
    endif
    completions += subcompletions

    if !empty(completions)
      asyncomplete#preprocess_complete(context, completions)
      return
    endif

    # Workaround; matchfuzzy() always returns list<string>
    var items: list<any> =
          matches_dict->values()->mapnew((_, val): list<any> => val.items)->flattennew()

    if trim(context.base) !=# ''
      items = matchfuzzy(items, context.base, {key: 'word'})
    endif
    asyncomplete#preprocess_complete(context, items)
  enddef

  g:asyncomplete_auto_popup = 1
  g:asyncomplete_preprocessor = [function('s:AsyncompletePreprocessor')]
  g:asyncomplete_auto_completeopt = 0
endif
if OptPluginExists('vim-lsp')
  # Lazy-loading
  augroup vimrc_lsp_lazyload
    autocmd BufReadPre * LoadLspPlugins()
    autocmd BufNewFile * {
      LoadLspPlugins()
      doautocmd lsp BufNewFile
    }
    if !v:vim_did_enter
      autocmd VimEnter * {
        # Do not load lsp plugins on git commit
        if argc() != 0 && argv()[0] !~# '\<COMMIT_EDITMSG\>$'
          LoadLspPlugins()
          doautocmd lsp BufReadPost
        endif
      }
    endif
  augroup END

  def LoadLspPlugins()
    # Load completion plugin first.
    if OptPluginExists('asyncomplete.vim')
      # asyncomplete-lsp.vim must be loaded before asyncomplete.vim
      if OptPluginExists('asyncomplete-lsp.vim')
        packadd asyncomplete-lsp.vim
      endif
      packadd asyncomplete.vim
    endif

    # vim-lsp-settings must be loaded before vim-lsp
    if OptPluginExists('vim-lsp-settings')
      packadd vim-lsp-settings
    endif
    packadd vim-lsp

    # Plugin initializations
    inoremap <expr> <C-n>
          \ pumvisible() ? "\<C-n>" : asyncomplete#close_popup() .. "\<C-x>\<C-n>"
    inoremap <expr> <C-p>
          \ pumvisible() ? "\<C-p>" : asyncomplete#close_popup() .. "\<C-x>\<C-p>"
    inoremap <expr> <C-e> pumvisible() ? asyncomplete#cancel_popup() : "\<C-e>"
    lsp#enable()

    autocmd! vimrc_lsp_lazyload
    command! -bar LoadLspPlugins {
      echohl WarningMsg
      echo 'Lsp plugins are already loaded'
      echohl NONE
    }
  enddef
  command! -bar LoadLspPlugins LoadLspPlugins()|echo 'Loaded lsp plugins'


  g:lsp_diagnostics_signs_enabled = 1
  g:lsp_diagnostics_signs_error = {'text': '>>'}
  g:lsp_diagnostics_signs_warning = {'text': '--'}
  g:lsp_diagnostics_signs_information = {'text': '--'}
  g:lsp_diagnostics_signs_hint = {'text': '!?'}
  g:lsp_diagnostics_enabled = 1
  g:lsp_diagnostics_float_delay = 10
  g:lsp_diagnostics_float_cursor = 1

  def LspDefineBufferAutocmds()
    LspClearBufferAutocmds()

    if &filetype ==# 'go'
      augroup vimrc_lsp_buffer_go
        autocmd BufWritePre <buffer> silent LspDocumentFormatSync
        autocmd BufWritePre <buffer> silent LspCodeActionSync source.organizeImports
        autocmd Filetype <buffer> ++once LspClearBufferAutocmds()
      augroup END
    endif

    augroup vimrc_lsp_buffer
      autocmd BufWritePost <buffer> lclose | silent LspDocumentDiagnostics
    augroup END
  enddef

  def LspClearBufferAutocmds()
    augroup vimrc_lsp_buffer
      autocmd! * <buffer>
    augroup END

    if &filetype ==# 'go'
      augroup vimrc_lsp_buffer_go
        autocmd! * <buffer>
      augroup END
    endif
  enddef

  def LspEnableForBuffer()
    setlocal omnifunc=lsp#complete
    nnoremap <buffer> <Plug>(<SID>-open-folding) zv
    nmap <buffer> <silent> gd <Plug>(lsp-declaration)<Plug>(<SID>-open-folding)

    LspDefineBufferAutocmds()
  enddef

  def LspDisableForBuffer()
    setlocal omnifunc=
    nunmap <buffer> gd

    LspClearBufferAutocmds()
  enddef

  augroup vimrc_lsp
    autocmd User lsp_buffer_enabled call LspEnableForBuffer()
    autocmd User lsp_buffer_disabled call LspDisableForBuffer()
  augroup END
endif
# Textobj/Operator-user plugins
# @param
# - plugin_name: the name of plugin
# - info: mapping-information. The keys are:
#   - rhs (string)
#   - lhs (string)
#   - modes (string)
# - on_load: the hook fired just after loading plugin
# TODO: Support Funcref as on_load?
def MapOperator(plugin_name: string, info_arg: dict<string>, on_load: string = '')
  var info = extend(info_arg, {modes: 'nxo'}, 'keep')
  MapTextModifierPlugin(plugin_name, info, on_load)
enddef

def MapTextobj(plugin_name: string, info_arg: dict<string>, on_load: string = '')
  var info = extend(info_arg, {modes: 'xo'}, 'keep')
  MapTextModifierPlugin(plugin_name, info, on_load)
enddef

def MapTextModifierPlugin(
      plugin_name: string, info: dict<string>, on_load: string = '')
  # When the plugin is already loaded, do not use loader mapping. This load
  # guard is mainly for vimrc-reloading
  if HasInString(&runtimepath, plugin_name)
    return
  endif

  for key in ['rhs', 'lhs', 'modes']
    if !has_key(info, key)
      EchomsgError('MapTextModifierPlugin: This key is missing: ' .. key)
      return
    endif
  endfor

  var loadermap = printf('<SID>LoadTextModifierPlugin("%s", %s, "%s")',
            plugin_name, string(info)->substitute('<', '<lt>', 'g'), on_load)
  for mode in info.modes
    execute mode .. 'map <expr>' info.lhs loadermap
  endfor
enddef

def LoadTextModifierPlugin(plugin: string, info: dict<string>, on_load: string): string
  if !(HasInString(&runtimepath, plugin .. ',') || &runtimepath[-len(plugin) :] == plugin)
    execute 'packadd' plugin

    if on_load !=# ''
      call(on_load, [])
    endif
  endif

  # "\<Plug>" -> '<Plug>'
  var rhs = info.rhs->substitute("\\C\<Plug>", '<Plug>', 'g')
  for mode in info.modes
    execute mode .. 'map' info.lhs rhs
  endfor

  # '<Plug>' -> "\<Plug>"
  return info.rhs->substitute('\c<Plug>', "\<Plug>", 'g')
enddef

if OptPluginExists('vim-operator-replace')
  MapOperator('vim-operator-replace', {lhs: 'ms', rhs: '<Plug>(operator-replace)'})
endif
if OptPluginExists('vim-operator-surround')
  MapOperator('vim-operator-surround',
      {lhs: 'ma', rhs: '<Plug>(operator-surround-append)'})
  MapOperator('vim-operator-surround',
      {lhs: 'md', rhs: '<Plug>(operator-surround-delete)'})
  MapOperator('vim-operator-surround',
      {lhs: 'mr', rhs: '<Plug>(operator-surround-replace)'})
endif
if OptPluginExists('caw.vim')
  inoremap <Plug>(vimrc:caw:prefix) <C-g>u<ESC>
  inoremap <silent> <Plug>(vimrc:caw:comment:here)
        \ <C-r>=b:caw_oneline_comment<CR>

  def CawMap(): string
    var kind = ''
    if col('.') == 1
      kind = 'zeropos'
    elseif col('.') == col('$')
      kind = 'dollarpos'
    elseif getline('.') =~# '^\s\+$'
      kind = 'hatpos'
    else
      return "\<Plug>(vimrc:caw:comment:here)"
    endif
    return "\<Plug>(vimrc:caw:prefix)\<Plug>(caw:" .. kind .. ":comment)"
  enddef
  imap <expr> <Plug>(vimrc:caw:map) <SID>CawMap()

  MapOperator('caw.vim', {lhs: 'mc', rhs: '<Plug>(caw:hatpos:toggle:operator)'})
  MapOperator('caw.vim', {lhs: 'm/', rhs: '<Plug>(caw:hatpos:toggle:operator)'})
  MapTextModifierPlugin('caw.vim', {lhs: '<C-l><C-j>', rhs: '<Plug>(vimrc:caw:map)', modes: 'i'})
  g:caw_no_default_keymappings = 1
  g:caw_dollarpos_sp_left = '  '
  g:caw_dollarpos_sp_right = ' '
  g:caw_hatpos_sp = ' '
  g:caw_zeropos_sp = ' '
endif
if OptPluginExists('vim-operator-swap')
  MapOperator('vim-operator-swap', {lhs: 'my', rhs: '<Plug>(operator-swap-marking)'})
  MapOperator('vim-operator-swap', {lhs: 'mp', rhs: '<Plug>(operator-swap)'})
endif
if OptPluginExists('vim-textobj-entire')
  MapTextobj('vim-textobj-entire', {lhs: 'aa', rhs: '<Plug>(textobj-entire-a)'})
  MapTextobj('vim-textobj-entire', {lhs: 'ia', rhs: '<Plug>(textobj-entire-i)'})
  g:textobj_entire_no_default_key_mappings = 1
endif
if OptPluginExists('vim-textobj-function')
  augroup vim_textobj_function
    autocmd Filetype c,java,vim ++once {
      packadd vim-textobj-function
      execute 'runtime ftplugin/vim-textobj-function/' .. expand('<amatch>') .. '/textobj-function.vim'
    }
  augroup END
  def CleanTextobjFunctionAutocmd()
    augroup vim_textobj_function
      autocmd!
    augroup END
  enddef
  MapTextobj('vim-textobj-function',
    {lhs: 'af', rhs: '<Plug>(textobj-function-a)'},
    'CleanTextobjFunctionAutocmd')
  MapTextobj('vim-textobj-function',
    {lhs: 'if', rhs: '<Plug>(textobj-function-i)'},
    'CleanTextobjFunctionAutocmd')
  g:textobj_function_no_default_key_mappings = 1
endif
if OptPluginExists('vim-textobj-indent')
  MapTextobj('vim-textobj-indent', {lhs: 'ai', rhs: '<Plug>(textobj-indent-a)'})
  MapTextobj('vim-textobj-indent', {lhs: 'aI', rhs: '<Plug>(textobj-indent-same-a)'})
  MapTextobj('vim-textobj-indent', {lhs: 'ii', rhs: '<Plug>(textobj-indent-i)'})
  MapTextobj('vim-textobj-indent', {lhs: 'iI', rhs: '<Plug>(textobj-indent-same-i)'})
  g:textobj_indent_no_default_key_mappings = 1
endif
if OptPluginExists('vim-textobj-line')
  MapTextobj('vim-textobj-line', {lhs: 'al', rhs: '<Plug>(textobj-line-a)'})
  MapTextobj('vim-textobj-line', {lhs: 'il', rhs: '<Plug>(textobj-line-i)'})
  g:textobj_line_no_default_key_mappings = 1
endif
if OptPluginExists('vim-textobj-commentblock')
  MapTextobj('vim-textobj-commentblock',
        {lhs: 'ac', rhs: '<Plug>(textobj-commentblock-a)'},
        'OnLoadingTextobjCommentblock')
  MapTextobj('vim-textobj-commentblock',
        {lhs: 'ic', rhs: '<Plug>(textobj-commentblock-i)'},
        'OnLoadingTextobjCommentblock')
  g:textobj_commentblock_no_default_key_mapings = 1

  def OnLoadingTextobjCommentblock()
    augroup vimrc_textobj_commentblock
      autocmd!
      autocmd Filetype * TextobjCommentblockPickerCaw()
    augroup END
    TextobjCommentblockPickerCaw()
  enddef

  def TextobjCommentblockPickerCaw()
    if exists('g:loaded_caw')
      textobj#commentblock#pick#caw()
    else
      textobj#commentblock#pick#commentblock()
    endif
  enddef
endif
if OptPluginExists('vim-textobj-between')
  MapTextobj('vim-textobj-between', {lhs: 'ad', rhs: '<Plug>(textobj-between-a)'})
  MapTextobj('vim-textobj-between', {lhs: 'id', rhs: '<Plug>(textobj-between-i)'})
  g:textobj_between_no_default_key_mappings = 1

  omap i/ id/
  omap a/ ad/
  vmap i/ id/
  vmap a/ ad/
endif

# ReadingVimrc
command! -bar ReadingVimrc call ReadingVimrc()
augroup vimrc-readingvimrc
  autocmd BufReadCmd readingvimrc://* OnOpenReadingVimrcBuffer()
augroup END
def ReadingVimrc()
  try
    packadd vim-reading-vimrc

    # Use :execute here because :ReadingVimrcNext command doesn't defined yet
    # when compiling this function and E476 error is given
    execute 'ReadingVimrcNext'

    command! ReadingVimrc ReadingVimrcNext
  catch
    EchomsgError(v:throwpoint .. v:exception)
  endtry
enddef

def OnOpenReadingVimrcBuffer()
  var bufname = expand('<amatch>')->matchstr('readingvimrc://\zs.*')
  if bufname ==# 'next'
    if winnr('$') == 1
      return
    endif

    var curwinnr = winnr()
    for winnr in range(1, winnr('$'))
      if winnr == curwinnr
        continue
      elseif line('$') > 1 || winbufnr(winnr)->getbufline(1)[0] !=# ''
        wincmd T
        return
      endif
    endfor

    only
  else
    vmap y <Plug>(reading_vimrc-update_clipboard)
    if winnr('$') > 1
      wincmd T
    endif
  endif
enddef

# Additional plugins
# Taking notes
# autocmd User vimrc_initialize ++once call vimrc#notes#load()
command! -bar -nargs=* MemoNew call vimrc#notes#memo_new(<q-args>)
command! -bar -nargs=+ -complete=customlist,vimrc#notes#memo_complete
      \ MemoDelete call vimrc#notes#memo_delete(<f-args>)
command! -bar MemoList call vimrc#notes#memo_list()
command! -bar -nargs=* OtameshiNew call vimrc#notes#otameshi_new(<q-args>)
command! -bar -nargs=+ -complete=customlist,vimrc#notes#otameshi_complete
      \ OtameshiDelete call vimrc#notes#otameshi_delete(<f-args>)
command! -bar OtameshiList call vimrc#notes#otameshi_list()


# working-plugin
command! -bar -nargs=+ -complete=customlist,vimrc#workingplugin#complete
      \ WorkingPluginLoad call vimrc#workingplugin#load(<f-args>)
command! -bar -bang -nargs=1 -complete=customlist,vimrc#workingplugin#complete
      \ WorkingPluginCd call vimrc#workingplugin#cd(<bang>0, <q-args>)
command! -bar -nargs=+ WorkingPluginClone call vimrc#workingplugin#clone(<f-args>)
command! -bar -nargs=+ WorkingPluginNew call vimrc#workingplugin#new(<f-args>)
command! -bar -nargs=+ -complete=customlist,vimrc#workingplugin#complete
      \ WorkingPluginRm call vimrc#workingplugin#rm(<f-args>)
# Sessions
final SessionDir = JoinPath(CacheDir, 'sessions')
{
  Mkdir(SessionDir)
  command! MkSession call vimrc#session#make()
  command! -nargs=+ -complete=custom,vimrc#session#complete DelSession
       \ call vimrc#session#delete(1, <f-args>)
  command! -nargs=1 -complete=custom,vimrc#session#complete RestoreSession
       \ call vimrc#session#restore(<q-args>)
}

# Showmode
def ShowmodeInit()
  var colors = {
    normal: [['22', '148'], ['#005f00', '#afdf00']],
    insert: [['23', '117'], ['#005f5f', '#87dfff']],
    visual: [['88', '208'], ['#870000', '#ff8700']],
    replace: [['231', '160'], ['#ffffff', '#df0000']],
  }
  for [mode, color] in items(colors)
    execute printf(
      'highlight VimrcShowMode%s ctermfg=%s ctermbg=%s guifg=%s guibg=%s',
      mode, color[0][0], color[0][1], color[1][0], color[1][1])
  endfor
enddef
def ShowmodeMode(): string
  ShowmodeHighlight()
  var map = {
    n: 'NORMAL',
    i: 'INSERT',
    R: 'REPLACE',
    v: 'VISUAL',
    V: 'V-LINE',
    ["\<C-v>"]: 'V-BLOCK',
    c: 'COMMAND',
    ce: 'EX-COM',
    s: 'SELECT',
    S: 'S-LINE',
    ["\<C-s>"]: 'S-BLOCK',
    t: 'T-INSERT',
    no: 'OPERATOR',
    niI: 'N-INSERT',
    niR: 'N-REPLACE',
    niV: 'N-V-REPLACE',
  }
  return get(map, mode(), 'UNKNOWN')
enddef
def ShowmodeHighlight()
  var type = get({
     i: 'insert',
     t: 'insert',
     R: 'replace',
     v: 'visual',
     V: 'visual',
     ["\<C-v>"]: 'visual',
   }, mode(), 'normal')
  execute 'highlight link VimrcShowMode VimrcShowMode' .. type
enddef
def ShowmodeLabel(): string
  if win_getid() == g:statusline_winid &&
        getwinvar(g:statusline_winid, '&modifiable')
    return '%#VimrcShowMode# %{<SID>ShowmodeMode()} %#Statusline#'
  endif
  return ''
enddef
augroup vimrc_showmode
  autocmd ColorScheme * ShowmodeInit()
  autocmd User vimrc_initialize ++once ShowmodeInit()
augroup END

# statusline
if PluginExists('gina.vim')
  augroup vimrc_gina
    autocmd CursorHold * ++once call gina#component#repo#branch() | redrawstatus
  augroup END
  def StatuslineGitBranch(): string
    if exists('*gina#component#repo#branch')
      var branch = gina#component#repo#branch()
      if branch ==# ''
        return 'no-git'
      else
        var ahead: any = gina#component#traffic#ahead()
        var behind: any = gina#component#traffic#behind()
        var staged = !!gina#component#status#staged()->str2nr()
        var unstaged = !!gina#component#status#unstaged()->str2nr()

        var icon_modified = (staged || unstaged) ? '*' : ''
        var icon_ahead =
              ahead->type() == v:t_number && !!ahead ? '↑' : ''
        var icon_behind =
              behind->type() == v:t_number && !!behind ? '↓' : ''
        return branch .. icon_modified .. icon_ahead .. icon_behind
      endif
    else
      return 'loading...'
    endif
  enddef
else
  def StatuslineGitBranch(): string
    return 'no-gina'
  enddef
endif
def StatuslineGenerator(): string
  var statusline =
    '%m' ..
    ShowmodeLabel() ..
    '[%{&ft ==# "" ? "No ft" : &ft}]' ..
    '[#%{bufnr("%")}]' ..
    '[%{<SID>StatuslineGitBranch()}]' ..
    '%{<SID>FilenameLabel(bufnr("%"))}' ..
    '%<%=' ..
    '[%{pathshorten(getcwd(winnr()))}]'
  return substitute(statusline, '\c<SID>', SIDPrefix(), 'g')
enddef
def FilenameLabel(bufnr: number): string
  var buftype = getbufvar(bufnr, '&buftype')
  var bufname = bufname(bufnr)
  if buftype ==# 'help'
    return fnamemodify(bufname, ':t')
  elseif buftype ==# 'quickfix'
    return '[quickfix]'
  elseif getbufvar(bufnr, '&previewwindow')
    return '[preview]'
  elseif buftype ==# 'terminal'
    return 'terminal:' .. bufname
  elseif buftype ==# 'prompt'
    return '[prompt]'
  else
    return (buftype ==# 'nofile' ? ' *NoFile* ' : '') ..
      (bufname ==# '' ? '[NoName]' : pathshorten(fnamemodify(bufname, ':.')))
  endif
enddef
&statusline = printf('%%!%sStatuslineGenerator()', SIDPrefix())

# tabline
&tabline = printf('%%!%sTabline()', SIDPrefix())
def GenerateTabinfo(tabnr: number): string
  var tablist = tabpagebuflist(tabnr)
  var info = ''
  info ..= len(filter(copy(tablist), 'getbufvar(v:val, "&mod")')) > 0 ? '[+]' : ''
  info ..= '[' .. tabpagewinnr(tabnr, '$') .. ']'
  return info
enddef
def Tabline(): string
  var tabline = '%#TabLine#|'
  var t = tabpagenr()

  for n in range(1, tabpagenr('$'))
    tabline ..= '%' .. n .. 'T'
    var info = ' ' .. GenerateTabinfo(n) .. ' '
    if t == n
      tabline ..= '%#TabLineSel# %999Xx%X' .. info .. '%#TabLine#'
    else
      tabline ..= info
    endif
    tabline ..= '%T|'
  endfor
  tabline ..= '%>%=[%{pathshorten(getcwd())}]'

  return substitute(tabline, '\c<SID>', SIDPrefix(), 'g')
enddef

# :terminal
augroup vimrc_terminal
  autocmd TerminalWinOpen * setlocal nonumber norelativenumber
augroup END

# EmphasisIndent
highlight link VimrcEmphasisIndent CursorLine
augroup vimrc_emphasize_indent
  autocmd WinEnter * EmphasizeIndent()
  autocmd OptionSet expandtab,smarttab,tabstop,shiftwidth EmphasizeIndent()
  autocmd User vimrc_initialize ++once EmphasizeIndent()
augroup END
def EmphasizeIndent()
  if exists('w:disable_emphasis_indent') && <bool>w:disable_emphasis_indent
    return
  endif
  if exists('w:emphasis_indent_id')
    matchdelete(w:emphasis_indent_id)
  endif

  var pat = '\v%%(^%%(%s)*)@<=%s'
  if &l:expandtab
    pat = printf(pat, repeat('\s', shiftwidth()), '\s')
  else
    pat = printf(pat, '\t\t', '\t')
  endif
  w:emphasis_indent_id = matchadd('VimrcEmphasisIndent', pat)
enddef
def EmphasisIndentEnable()
  w:disable_emphasis_indent = 0
  EmphasizeIndent()
enddef
def EmphasisIndentDisable()
  w:disable_emphasis_indent = 0
  if exists('w:emphasis_indent_id')
    matchdelete(w:emphasis_indent_id)
    unlet w:emphasis_indent_id
  endif
enddef
command! EmphasisIndentDisable call EmphasisIndentDisable()
command! EmphasisIndentEnable call EmphasisIndentEnable()

# WarningSpace
highlight link WarningSpace Error
augroup vimrc_warningspace
  autocmd WinEnter * WarningSpace()
  autocmd OptionSet * WarningSpace()  # TODO: specify option?
  autocmd User vimrc_initialize ++once WarningSpace()
augroup END
def WarningSpace()
  # Clean up.
  if exists('w:twobyte_space_id')
    matchdelete(w:twobyte_space_id)
    unlet w:twobyte_space_id
  endif
  if exists('w:end_of_line_space_id')
    matchdelete(w:end_of_line_space_id)
    unlet w:end_of_line_space_id
  endif

  if &buftype !=# '' || !&modifiable
    return
  endif

  # Zenkaku space
  # NOTE: '\%d12288' means one zenkaku space. HINT: nr2char(12288)
  w:twobyte_space_id = matchadd('WarningSpace', '\%d12288')

  # White spaces in the end of line
  w:end_of_line_space_id = matchadd('WarningSpace', '\s\+$')
enddef
command! ReplaceTwobyteSpace keeppatterns :%s/\%d12288/ /g
command! DeleteLineEndSpace keeppatterns :%s/\s\+$//g

# mru
g:mru_history_file = JoinPath(CacheDir, 'mru', 'history')
{
  var dir_ = JoinPath(CacheDir, 'mru')
  if !isdirectory(dir_)
    Mkdir(dir_)
  endif
}
g:mru_ignore_pattern = [
      \ '\.git\>',
      \ '^\V\%(' .. escape(expand('~'), '\') .. '\)\@!'
      \ ]

# filore
def FiloreMapping()
  var mapping: list<any> = [
   ['q', 'exit'],
   ['o', 'toggle-directory-folding'],
   ['l', 'enter-directory'],
   ['h', 'leave-directory'],
   ['.', 'toggle-show-hidden-files'],
   ['k', 'loop-cursor-up'],
   ['j', 'loop-cursor-down'],
   ['<CR>', 'open-file'],
   ['/', 'filter-files'],
   ['<C-h>', 'start-history'],
  ]->mapnew((_: number, val: list<string>): string =>
       ('nmap <buffer> ' .. val[0] .. ' <Plug>(filore-' .. val[1] .. ')'))
  execute join(mapping, "\n")
enddef
augroup vimrc_additional_plugins
  autocmd FileType filore call FiloreMapping()
augroup END

# git
def GitDiffGetcmd(arg_target: string): string
  var target: string = resolve(arg_target)
  var gitroot: string = FindGitroot(target)
  if gitroot ==# ''
    return ''
  endif
  return printf('git -C %s --no-pager diff --no-color %s',
          gitroot, target)
enddef

def GitDiff(arg_target: string): void
  var target: string
  if arg_target ==# ''
    target = bufname('%')
  else
    target = arg_target
  endif
  target = resolve(target)
  if getftype(target) ==# ''
    EchomsgError('File or directory does not exists: ' .. target)
    return
  endif
  var cmd: string = GitDiffGetcmd(target)
  if cmd ==# ''
    return
  endif
  var bufnr = term_start(cmd, {
          term_name: '[git diff] ' .. fnamemodify(target, ':~:.'),
          norestore: 1,
        })
  if bufnr != 0
    setlocal nocursorline nocursorcolumn filetype=diff
    nnoremap <buffer> <nowait> q <Cmd>quit<CR>
    cursor(1, 0)
  endif
  return
enddef
command! -nargs=? -complete=file GitDiff call GitDiff(<q-args>)

# gyoza
augroup vimrc_gyoza
  autocmd User vimrc_initialize ++once vimrc#gyoza#enable()
augroup END

# splash
command! Splash call vimrc#splash#show()
if !v:vim_did_enter && !has('gui_running')
  augroup vimrc_splash
    autocmd!
    autocmd VimEnter * ++once ++nested call vimrc#splash#intro()
    autocmd StdinReadPre * ++once autocmd! vimrc_splash VimEnter
  augroup END
endif

# gvimrc
if has('gui_running')
  if !v:vim_did_enter
    if IsLinux && executable('i3')
      # It seems that too large window on i3 does not work properly.
      set lines=50
      set columns=200
    else
      set lines=999
      set columns=9999
    endif
  endif
  set guioptions& guioptions-=e guioptions-=T guioptions-=m
  set guioptions-=R guioptions-=r guioptions-=L guioptions-=l
  set mouse=a
  set nomousefocus
  set mousehide

  if has('win32')
    set guifont=Cica:h14,MS_Gothic:h10:cSHIFTJIS
    set linespace=1
  elseif has('mac')
    set guifont=Cica:h14,Osaka-Mono:h14
  elseif has('xfontset')
    #for unix (use xfontset)
    set guifont=a14,r14,k14
  elseif has('linux')
    set guifont=Cica\ 14,DejaVu\ Sans\ Mono\ 14
  endif

  if has('multi_byte_ime') || has('xim')
    set iminsert=0 imsearch=0
    augroup vimrc_iminsert
      autocmd InsertLeave * set iminsert=0
    augroup END
  endif
endif

# lvimrc
{
  var lvimrc_ = Rc('lvimrc')
  lvimrc_ = JoinPath('~', lvimrc_)
  execute 'command! -bar -nargs=* LVimrc ' ..
          'execute (<q-args> ==# "" ? "edit" : <q-args>)' string(lvimrc_)
  if filereadable(expand(lvimrc_))
    execute 'source' lvimrc_
  endif
}

# Initialize when loading this file.
augroup vimrc_initialize_dummy
  # Not to provide an error. For more information, see `:h E217`
  autocmd User vimrc_initialize ++once # Do nothing.
augroup END

if v:vim_did_enter
  doautocmd User vimrc_initialize
else
  augroup vimrc_initialize
    autocmd VimEnter * ++once doautocmd User vimrc_initialize
  augroup END
endif