【2023大掃除】vimrcにたまった機能をプラグインに吐き出す【vimプラグイン】

# はじめに
この記事は[クソコードお焚き上げの会 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/anti-code-fire)の15日目の記事です。

vimrcにたまった機能の掃除ついでに、入れてるプラグイン一覧紹介します。

|環境||
|--|--|
|PC|<img src="https://img.shields.io/badge/-Apple-000000.svg?logo=apple"> MacBook Pro M2|
|OS|<img src="https://img.shields.io/badge/--000000.svg?logo=macOS"> macOS 14.1.1（23B81）|
|editor|<img src="https://img.shields.io/badge/-Vim-019733.svg?logo=vim"> vim 9.0|


# 🧹大掃除🧹
クソコードの中でも、<font color="red">**なげぇコード**</font>を供養しました。
まずはこちらのレポのファイルをご覧ください。

https://github.com/serna37/vim

[こちら](https://github.com/serna37/vim/blob/master/.vimrc)が対象のファイルです。

```
3666 lines (3356 loc) · 136 KB
```

さ、さささ三千行！！？！？
おおすぎる！！！😣😣😣

これは『プラグインが使えない環境』を想定し
たとえ`GitBash`の`MinGW`だとしても、プラグインありみたいなvimを使いたい！
というわがままから来ています。

様々なプラグイン機能を`vimscript`で真似してつくり、ぶち込みまくっていました。


# 独自実装で置き換えていたもの
- [simeji/winresizer](https://github.com/simeji/winresizer)
- [jiangmiao/auto-pairs](https://github.com/jiangmiao/auto-pairs)
- [tpope/vim-surround](https://github.com/tpope/vim-surround)
- [vim-airline/vim-airline](https://github.com/vim-airline/vim-airline)
- [junegunn/fzf.vim](https://github.com/junegunn/fzf.vim)
- [junegunn/fzf](https://github.com/junegunn/fzf)
- [preservim/nerdtree](https://github.com/preservim/nerdtree)
- [yuttie/comfortable-motion.vim](https://github.com/yuttie/comfortable-motion.vim)
- [easymotion/vim-easymotion](https://github.com/easymotion/vim-easymotion)
- [unblevable/quick-scope](https://github.com/unblevable/quick-scope)
- [t9md/vim-quickhl](https://github.com/t9md/vim-quickhl)
- [MattesGroeger/vim-bookmarks](https://github.com/MattesGroeger/vim-bookmarks)
- [junegunn/goyo.vim](https://github.com/junegunn/goyo.vim)
- [mhinz/vim-startify](https://github.com/mhinz/vim-startify)
- [markonm/traces.vim](https://github.com/markonm/traces.vim)
- [thinca/vim-quickrun](https://github.com/thinca/vim-quickrun)
- [joshdick/onedark.vim](https://github.com/joshdick/onedark.vim)


# 独自で作っていた機能
- Running Cat (猫が走るアニメーションする)
- Popup terminal/git (ポップアップでterminalやlazygitする)
- Tab Anchor (5行移動と、アンカーをサイン)
- Search Current File fzf (現在バッファ内をfzfポップアップ検索)
- Grep Recursive Interactive (対話で範囲等を選択しGrep)
- IDE menu (IDE系のあんま使わない機能をポップアップメニュー)
- Completion (補完リソースは全範囲、重い)
- PlugInstall / PlugUnInstall (junegunn/vim-plugの真似ではある)
- Training Wheels Protocol (チュートリアルアニメーション)


# 供養する
一部を紹介していきます。長いので基本的にソースは折りたたみます。

### [vim-airline/vim-airline](https://github.com/vim-airline/vim-airline)
airlineもですが、[vim-fugitive](https://github.com/tpope/vim-fugitive)のようにgit statusを入れ込むこともしています。
が、なんだかうまくいってない部分もあります。　
我らが`tpop`氏はさすがですね、スター送りましょう。

https://github.com/tpope

<details><summary>プログラム</summary><div>

```vim
" {{{
let g:right_arrow = ''
let g:left_arrow = ''
let powerline_chk_mac = system('fc-list | grep powerline | wc -l')->trim()
let powerline_chk_win = system('cd /C/Windows/Fonts && ls | grep powerline | wc -l')->trim()
if !powerline_chk_mac+0 && !powerline_chk_win+0
    let g:right_arrow = ' '
    let g:left_arrow = ' '
endif

let g:modes = {
    \ 'i': ['#User_blackfg_bluebg_bold#', '#User_bluefg_blackbg#', 'INSERT'],
    \ 'n': ['#User_blackfg_greenbg_bold#', '#User_greenfg_blackbg#', 'NORMAL'],
    \ 'R': ['#User_blackfg_redbg_bold#', '#User_redfg_blackbg#', 'REPLACE'],
    \ 'c': ['#User_blackfg_greenbg_bold#', '#User_greenfg_blackbg#', 'COMMAND'],
    \ 't': ['#User_blackfg_redbg_bold#', '#User_redfg_blackbg#', 'TERMIAL'],
    \ 'v': ['#User_blackfg_pinkbg_bold#', '#User_pinkfg_blackbg#', 'VISUAL'],
    \ 'V': ['#User_blackfg_pinkbg_bold#', '#User_pinkfg_blackbg#', 'VISUAL'],
    \ "\<C-v>": ['#User_blackfg_pinkbg_bold#', '#User_pinkfg_blackbg#', 'VISUAL'],
    \ }

let g:ff_table = {'dos' : 'CRLF', 'unix' : 'LF', 'mac' : 'CR'}

let g:gitinf = 'no git '
fu! s:gitinfo() abort
    if !executable('git')
        retu
    endif
    let status = system('cd '.expand('%:h').' && git status')->trim()
    if status =~ "^fatal:"
        let g:gitinf = 'no repo '
        retu
    endif
    " TODO airline gitstatus結果を使いまわした方が早い？ -> いいだろべつに
    " TODO airline ls-filesに変える
    " TODO airline async refresh
    " TODO airline once get system cmd, trim it on vimscript
    " TODO terminalから戻った時とかに、表示変になるぞ
    let cmd = "cd ".expand('%:h')." && git status --short | awk -F ' ' '{print($1)}' | grep -c "
    let a = trim(system(cmd."'A'"))
    let aa = a !='0'?'+'.a :''
    let m = trim(system(cmd."-e 'M' -e 'D'"))
    let mm = m !='0'?'!'.m :''
    let nw = trim(system(cmd."'??'"))
    let nwnw = nw !='0'?'?'.nw :''
    let er = trim(system(cmd."'U'"))
    let ee = er !='0'?'✗'.er :''
    let g:gitinf = trim(system("cd ".expand('%:h')." && git branch | awk -F '*' '{print($2)}'")).join([aa,mm,nwnw,ee],' ')
endf

fu! g:SetStatusLine() abort
    let mode = get(g:modes, mode(), ['#User_blackfg_redbg_bold#', '#User_redfg_blackbg#', 'SP'])
    " start menu BTR
    if &filetype == 'Bocchi_The_Rock'
        let mode = ['#User_blackfg_greenbg_bold#', '#User_greenfg_blackbg#', 'BTR_start_menu']
    endif
    retu '%'.mode[0].' '.mode[2].' '.'%'.mode[1].g:right_arrow.'%#User_blackfg_graybg#'.g:right_arrow
        \ .'%#User_greenfg_graybg# %<%f%m%r%h%w %#User_grayfg_blackbg#'.g:right_arrow
        \ .'%#User_greenfg_blackbg# %{g:gitinf}%*'.g:right_arrow
        \ .'%* %='
        \ .'%*'.g:left_arrow.'%#User_greenfg_blackbg# %{&filetype}'
        \ .' %#User_grayfg_blackbg#'.g:left_arrow.'%#User_greenfg_graybg# %p%% %l/%L %02v%#User_blackfg_graybg#'.g:left_arrow
        \ .'%'.mode[1].g:left_arrow.'%'.mode[0].' [%{&fenc!=""?&fenc:&enc}][%{g:ff_table[&ff]}] %*'
endf
set stl=%!g:SetStatusLine()

" tabline
fu! s:buffers_label() abort
    " airline TODO リファクタ
    let b = ''
    for v in split(execute('ls'), '\n')->map({ _,v -> split(v, ' ')})
        let x = copy(v)->filter({ _,v -> !empty(v) })
        if stridx(x[1], 'F') == -1 && stridx(x[1], 'R') == -1
            let hi = stridx(x[1], '%') != -1 ? '%#User_blackfg_greenbg#' : '%#User_greenfg_graybg#'
            let hiar = stridx(x[1], '%') != -1 ? '%#User_greenfg_blackbg#' : '%#User_grayfg_blackbg#'
            let hiarb = stridx(x[1], '%') != -1 ? '%#User_blackfg_greenbg#' : '%#User_blackfg_graybg#'
            if x[2] == '+'
                let hi = '%#User_blackfg_bluebg#'
                let hiar = '%#User_bluefg_blackbg#'
                let hiarb = '%#User_blackfg_bluebg#'
            endif
"[^/]*$
            let f = x[2] == '+' ? '✗'.matchstr(join(split(x[3],'"'),''),'[^/]*$') : matchstr(join(split(x[2],'"'),''),'[^/]*$')
            let b = b.'%'.x[0].'T'.hiarb.g:right_arrow.hi.f.hiar.g:right_arrow
        endif
    endfor
    retu b
endf
fu! s:tabpage_label(n) abort
    let hi = a:n is tabpagenr() ? '%#User_blackfg_greenbg#' : '%#User_greenfg_graybg#'
    let bufnrs = tabpagebuflist(a:n)
    let no = len(bufnrs)
    if no is 1
        let no = ''
    endif
    let mod = len(filter(copy(bufnrs), 'getbufvar(v:val, "&modified")')) ? '✗' : ''
    let fname = pathshorten(bufname(bufnrs[tabpagewinnr(a:n) - 1]))
    retu '%'.a:n.'T'.hi.no.mod.fname.' ⁍|'.'%T%#TabLineFill#'
endf
fu! g:SetTabLine() abort
    if tabpagenr('$') == 1
        retu s:buffers_label()
    endif
    retu range(1,tabpagenr('$'))->map('s:tabpage_label(v:val)')->join(' ').' %#TabLineFill#%T'
endf
set tabline=%!g:SetTabLine()

fu! s:moveBuf(flg) abort
    let current_id = ''
    let buf_arr = []
    for v in split(execute('ls'), '\n')->map({ _,v -> split(v, ' ')})
        let x = copy(v)->filter({ _,v -> !empty(v) })
        if stridx(x[1], 'F') == -1 && stridx(x[1], 'R') == -1
            cal add(buf_arr, x[0])
            if stridx(x[1], '%') != -1
                let current_id = x[0]
            endif
        endif
    endfor
    let buf_idx = a:flg == 'next' ? match(buf_arr, current_id) + 1 : match(buf_arr, current_id) - 1
    let buf_id = buf_idx == len(buf_arr) ? buf_arr[0] : buf_arr[buf_idx]
    exe 'b '.buf_id
endf

fu! s:closeBuf() abort
    let now_b = bufnr('%')
    " TODO airline not key, you should call function
    execute("norm \<C-n>")
    execute('bd ' . now_b)
endf

aug user_onedark
    au!
    au ColorScheme * hi User_greenfg_blackbg ctermfg=114 ctermbg=235
    au ColorScheme * hi User_greenfg_graybg ctermfg=114 ctermbg=238
    au ColorScheme * hi User_bluefg_blackbg ctermfg=39 ctermbg=235
    au ColorScheme * hi User_pinkfg_blackbg ctermfg=169 ctermbg=235
    au ColorScheme * hi User_redfg_blackbg ctermfg=203 ctermbg=235
    au ColorScheme * hi User_grayfg_blackbg ctermfg=238 ctermbg=235
    au ColorScheme * hi User_blackfg_graybg ctermfg=235 ctermbg=238
    au ColorScheme * hi User_blackfg_greenbg ctermfg=235 ctermbg=114
    au ColorScheme * hi User_blackfg_greenbg_bold cterm=bold ctermfg=234 ctermbg=114
    au ColorScheme * hi User_blackfg_bluebg ctermfg=235 ctermbg=39
    au ColorScheme * hi User_blackfg_bluebg_bold cterm=bold ctermfg=234 ctermbg=39
    au ColorScheme * hi User_blackfg_pinkbg_bold cterm=bold ctermfg=234 ctermbg=170
    au ColorScheme * hi User_blackfg_redbg_bold cterm=bold ctermfg=234 ctermbg=204
aug END

aug status_tabLine
    au!
    au BufWinEnter,BufWritePost * cal s:gitinfo()
aug END

noremap <silent><Plug>(buf-prev) :<C-u>cal <SID>moveBuf('prev')<CR>
noremap <silent><Plug>(buf-next) :<C-u>cal <SID>moveBuf('next')<CR>
noremap <silent><Plug>(buf-close) :<C-u>cal <SID>closeBuf()<CR>
" }}}
```
</div></details>

これは、ステータスラインに表示する`git status`を、VSCodeチックにしたりと様々工夫をしましたが
gitレポでないときのハンドリングが不十分だったり、vimモードの別ウィンドウのモードが反映されちゃったりといろいろ中途半端でした。
やっぱ本家のairlineはすごいんだなと思わされた逸品。

### [junegunn/fzf.vim](https://github.com/junegunn/fzf.vim)
および、[yuki-yano/fzf-preview.vim](https://github.com/yuki-yano/fzf-preview.vim)を真似しました。
正直、完成度はかなり高いと思っていますが、本家が`terminal`でのポップアップなのに対し
`vimのpopup`で無理やり再現しているため、もろもろの小さな手の届かなさが…。

さすがは[vim-plug](https://github.com/junegunn/vim-plug)や[goyo(禅モード)](https://github.com/junegunn/goyo.vim)なども手掛けている`junegunn`さんの最高傑作（私談）です。スター送りましょう。

https://github.com/junegunn

<details><summary>プログラム</summary><div>

```vim
" ===================================================================
" junegunn/fzf.vim
" ===================================================================
" {{{
" usage
" let arguments_def = #{
"     \ title: popup title as String,
"     \ list: search targets as List,
"     \ type: format in list. f, lm, flm (file, line, msg),
"     \ eprfx: enter zone prefix text as String (0 to default value),
"     \ }
" and call like this.
" cal s:fzsearch.popup(arguments_def)

let s:fzsearch = #{bwid: 0, ewid: 0, rwid: 0, pwid: 0, tid: 0, res: [],
    \ ffdict: #{vimrc: 'vim', zshrc: 'sh', js: 'javascript', py: 'python', md: 'markdown',
        \ ts: 'typescript', tsx: 'typescriptreact',
        \ },
    \ }

let s:fzsearch.allow_exts = glob($VIMRUNTIME.'/ftplugin/*.vim')->split('\n')
            \ ->map({_,v->matchstr(v,'[^/]\+\.vim')->split('\.')[0]})

fu! s:fzsearch.popup(v) abort
    if empty(a:v.list) || (len(a:v.list) == 1 && empty(a:v.list[0]))
        cal s:echoE('no data')
        retu
    endif

    let self.list = a:v.list
    let self.type = a:v.type
    " TODO depends on type
    let self.prr = a:v.eprfx
    let self.wd = ''
    let self.wda = []
    let self.ridx = 0
    let self.max = 30
    let self.mode = 'Fuzzy'
    let self.pr = '['.self.mode.']'.(self.prr ?? '>>')
    let self.res = a:v.list[0:self.max]

    let self.bwid = popup_create([], #{title: ' '.a:v.title.' ',
        \ zindex: 50, mapping: 0, scrollbar: 0,
        \ border: [], borderchars: ['─','│','─','│','╭','╮','╯','╰'], borderhighlight: ['FzBWin'],
        \ minwidth: &columns*9/12, maxwidth: &columns*9/12,
        \ minheight: &lines/2+6, maxheight: &lines/2+6,
        \ line: &lines/4-2, col: &columns/8+1,
        \ })

    let self.ewid = popup_create(self.pr, #{title: ' Search Mode: <Tab> | ClearText: <C-w> ',
        \ zindex: 100, mapping: 0,
        \ border: [], borderchars: ['─','│','─','│','╭','╮','╯','╰'], borderhighlight: ['FzEWin'],
        \ minwidth: &columns/3, maxwidth: &columns/3,
        \ minheight: 1, maxheight: 1,
        \ line: &lines*3/4+2, col: &columns/7+1,
        \ filter: function(self.fil, [#{wd: []}]),
        \ })
    cal matchaddpos('FzEWin', [[1, 1, len(self.pr)]],  16, -1, #{window: self.ewid})
    cal matchadd('DarkRed', '\[.*\]', 17, -1, #{window: self.ewid})

    let self.rwid = popup_menu(self.res, #{title: ' Choose: <C-n/p> <CR> | QuickFix: <C-q> ',
        \ zindex: 99, mapping: 0, scrollbar: 1,
        \ border: [], borderchars: ['─','│','─','│','╭','╮','╯','╰'], borderhighlight: ['FzWin'],
        \ minwidth: &columns/3, maxwidth: &columns/3,
        \ minheight: &lines/2, maxheight: &lines/2,
        \ pos: 'topleft', line: &lines/4, col: &columns/7,
        \ callback: 'Fzsearch_confirm',
        \ filter: function(self.jk, [0]),
        \ })

    " result list syntax
    if self.type == 'lm'
        cal setbufvar(winbufnr(self.rwid), '&filetype', &filetype)
    elseif self.type == 'flm'
        cal setbufvar(winbufnr(self.rwid), '&filetype', 'txt')
    endif

    " get preview file
    let path = bufname('%')
    if self.type =~ 'flm'
        let path = split(self.res[0], '|')[0]
    elseif self.type =~ 'f'
        let path = substitute(self.res[0], '\~', $HOME, 'g')
    endif
    let read = ['Cannot open file.', 'please check this file path.', path]
    try
        let read = readfile(path)
    catch
    endtry

    " preview
    let self.pwid = popup_menu(read, #{title: ' File Preview | Scroll: <C-d/u> ',
        \ zindex: 98, mapping: 0, scrollbar: 1,
        \ border: [], borderchars: ['─','│','─','│','╭','╮','╯','╰'], borderhighlight: ['FzWin'],
        \ minwidth: &columns/3, maxwidth: &columns/3,
        \ minheight: &lines/2+3, maxheight: &lines/2+3,
        \ pos: 'topleft', line: &lines/4, col: &columns/2+1,
        \ firstline: 1,
        \ filter: function(self.pv, [0]),
        \ })

    " get preview ext for syntax highlight
    let ext = matchstr(path, '[^\.]\+$')
    let ext = ext !~ '^[a-zA-Z0-9]\+$' ? '' : ext
    let ext = get(self.ffdict, ext, ext)
    cal setbufvar(winbufnr(self.pwid), '&filetype', self.type =~ 'f' ? ext : &filetype)

    " first line
    let lnm = 1
    if self.type == 'flm'
        let sep = split(self.res[0], '|')
        let lnm = len(sep) < 2 ? 1 : split(sep[1], ' ')[0]
    elseif self.type == 'lm'
        let lnm = split(self.res[0], ':')[0]
    endif
    " cursor line top from 10row
    cal popup_setoptions(self.pwid, #{firstline: (lnm-10 > 0 ? lnm-10 : 1)})
    cal win_execute(self.pwid, 'exe '.lnm)
endf

" on confirm
fu! Fzsearch_confirm(wid, idx) abort
    if a:idx == -1
        cal s:echoE('cancel')
        cal s:fzsearch.finalize()
        retu
    endif
    let result = s:fzsearch.res[a:idx-1]

    if s:fzsearch.type == 'f'
        exe 'e '.result
    elseif s:fzsearch.type == 'lm'
        let l = split(result, ':')[0]
        cal s:echoI('jump to '.l)
        exe l
        if s:fzsearch.mode == 'Simply'
            let @/ = s:fzsearch.wd
            norm! n
            cal s:quickhl.set()
        endif
    elseif s:fzsearch.type == 'flm'
        let sep = split(result, '|')
        let fnm = substitute(sep[0], $HOME, '~', 'g')
        let lnm = len(sep) < 2 ? 1 : split(sep[1], ' ')[0]
        " if quickfix window
        if expand('%')->empty()
            wincmd w
        endif
        exe 'e '.fnm
        exe lnm
        cal s:echoI('jump to '.fnm.' line:'.lnm)
    endif
    cal s:fzsearch.finalize()
endf

" create quickfix
fu! s:fzsearch.quickfix() abort
    let tmp = &errorformat
    let resource = self.res
    let ef = '%f: %l: %m'

    if self.type == 'lm'
        let fnm = expand('%')
        let resource = deepcopy(self.res)->map({_,v->fnm.': '.v})
    elseif self.type == 'f'
        let resource = deepcopy(self.res)->map({_,v->v.': 1: open'})
    elseif self.type == 'flm'
        " sample
        " filename|100 col 10-15|message
        " want-> filename, 100, message
        let resource = deepcopy(self.res)->map({_,v
                    \ ->split(v,'|')[0].': '
                    \ .split(split(v,'|')[1], ' ')[0].': '
                    \ .join(split(v,'|')[2:], '')})
    endif

    let &errorformat = ef
    cgetexpr resource | cw
    let &errorformat = tmp
    cal s:fzsearch.finalize()
endf

" finalize
fu! s:fzsearch.finalize() abort
    let s:fzsearch.list = []
    let s:fzsearch.res = []
    cal s:runcat.stop()
    cal popup_clear()
endf

" preview scroll
fu! s:fzsearch.scroll(wid, vector) abort
    if self.tid
        retu
    endif
    cal timer_stop(self.tid)
    let vec = a:vector ? "\<C-n>" : "\<C-p>"
    let self.tid = timer_start(10, { -> popup_filter_menu(a:wid, vec) }, #{repeat: -1})
    let delay = 600
    cal timer_start(delay, self.scstop)
endf

fu! s:fzsearch.scstop(_) abort
    cal timer_stop(self.tid)
    let self.tid = 0
endf

" preview draw update
fu! s:fzsearch.pvupd() abort
    let win = winbufnr(self.pwid)

    if empty(self.res) && self.type == 'lm'
        retu
    endif
    if self.type == 'lm'
        let lnm = split(self.res[self.ridx], ':')[0]
        cal popup_setoptions(self.pwid, #{firstline: (lnm-10 > 0 ? lnm-10 : 1)})
        cal win_execute(self.pwid, 'exe '.lnm)
        retu
    endif

    " del
    sil! cal deletebufline(win, 1, getbufinfo(win)[0].linecount)
    " put
    if empty(self.res)
        retu
    endif
    let path = self.type == 'flm'
                \ ? split(self.res[self.ridx], '|')[0]
                \ : substitute(self.res[self.ridx], '\~', $HOME, 'g')
    let read = ['Cannot open file.', 'please check this file path.', path]
    try
        let read = readfile(path)
    catch
    endtry
    cal setbufline(win, 1, read)
    " syntax
    let ext = matchstr(path, '[^\.]\+$')
    let ext = ext !~ '^[a-zA-Z0-9]\+$' ? '' : ext
    let ext = get(self.ffdict, ext, ext)
    cal setbufvar(win, '&filetype', ext)
    " line
    let sep = split(self.res[self.ridx], '|')
    let lnm = len(sep) < 2 ? 1 : split(sep[1], ' ')[0]
    cal popup_setoptions(self.pwid, #{firstline: (lnm-10 > 0 ? lnm-10 : 1)})
    cal win_execute(self.pwid, 'exe '.lnm)
endf

" search result update
fu! s:fzsearch.list_upd() abort
    " upd match result list
    let filterd = empty(self.wd) ? self.list :
                \ self.mode == 'Fuzzy' ? matchfuzzy(self.list, self.wd)
                \ : copy(self.list)->filter({_,v->match(v, self.wd)!=-1})
    ""let filterd = copy(self.list)->filter({_,v->match(v, self.wd)!=-1})
    let self.res = filterd[0:self.max]
    let self.ridx = len(self.res)-1 < self.ridx ? len(self.res)-1 : self.ridx
    cal setbufline(winbufnr(self.ewid), 1, self.pr . self.wd)
    cal deletebufline(winbufnr(self.rwid), 1, self.max)
    echo ''
    cal setbufline(winbufnr(self.rwid), 1, self.res)
    " highlight match char
    cal clearmatches(self.rwid)
    if !empty(self.wd)
        cal matchadd('FzMatch', self.mode == 'Fuzzy'
                    \ ? printf('\c[%s]', escape(self.wd, '\[\]\-\.\*'))
                    \ : '\c'.self.wd,
                    \ 16, -1, #{window: self.rwid})
    endif
    " upd preview
    cal self.pvupd()
endf

fu! s:fzsearch.togglemode() abort
    let self.mode = self.mode == 'Fuzzy' ? 'Simply' : 'Fuzzy'
    let self.pr = '['.self.mode.']'.(self.prr ?? '>>')
    cal clearmatches(self.ewid)
    cal matchaddpos('FzEWin', [[1, 1, len(self.pr)]],  16, -1, #{window: self.ewid})
    cal matchadd('DarkRed', '\[.*\]', 17, -1, #{window: self.ewid})
    cal self.list_upd()
endf

" enter search word
fu! s:fzsearch.fil(ctx, wid, key) abort
    if a:key is# "\<Esc>"
        cal popup_close(self.bwid)
        cal popup_close(self.pwid)
        cal popup_close(self.ewid)
        cal feedkeys("\<C-c>")
        retu 1
    elseif a:key is# "\<C-n>" || a:key is# "\<C-p>" || a:key is# "\<CR>"
        " only largest zindex window is active.
        cal popup_setoptions(self.rwid, #{zindex: 100})
        cal popup_setoptions(self.ewid, #{zindex: 99})
        cal popup_setoptions(self.pwid, #{zindex: 98})
        cal feedkeys(a:key)
        retu 1
    elseif a:key is# "\<C-d>" || a:key is# "\<C-u>"
        cal popup_setoptions(self.pwid, #{zindex: 100})
        cal popup_setoptions(self.ewid, #{zindex: 99})
        cal popup_setoptions(self.rwid, #{zindex: 98})
        cal feedkeys(a:key)
        retu 1
    elseif a:key is# "\<Tab>"
        cal self.togglemode()
        retu 1
    elseif a:key is# "\<C-q>"
        cal self.quickfix()
        cal feedkeys("\<Esc>")
        retu 1
    " TODO fzf.vim copy shold D-v only or Shift Insert. C-v want split
    " TODO fzf.vim add feature: refresh cache (or re search)
    elseif a:key is# "\<C-v>" || a:key is# "\<D-v>"
        for i in range(0, strlen(@+)-1)
            cal add(self.wda, strpart(@+, i, 1))
        endfor
    " TODO see self
    elseif a:key is# "\<BS>" && !empty(self.wda)
        unlet self.wda[len(self.wda)-1]
    elseif a:key is# "\<BS>" && len(self.wda) == 0
        " noop
        retu 1
    elseif a:key is# "\<C-w>"
        let self.wda = []
    elseif strtrans(a:key) == "<80><fd>`"
        " noop (for polyglot bug adhoc)
        retu 1
    else
        cal add(self.wda, a:key)
    endif

    let self.wd = join(self.wda, '')
    cal self.list_upd()
    retu a:key is# "x" || a:key is# "\<Space>" ? 1 : popup_filter_menu(a:wid, a:key)
endf

" choose result
fu! s:fzsearch.jk(_, wid, key) abort
    if a:key is# "\<Esc>"
        cal popup_close(self.bwid)
        cal popup_close(self.pwid)
        cal popup_close(self.ewid)
        cal feedkeys("\<C-c>")
    elseif a:key is# "\<C-n>"
        let self.ridx = self.ridx == len(self.res)-1 ? len(self.res)-1 : self.ridx+1
        cal self.pvupd()
        retu popup_filter_menu(a:wid, a:key)
    elseif a:key is# "\<C-p>"
        let self.ridx = self.ridx ? self.ridx-1 : 0
        cal self.pvupd()
        retu popup_filter_menu(a:wid, a:key)
    elseif a:key is# "\<CR>"
        cal popup_close(self.ewid)
        cal popup_close(self.pwid)
        cal popup_close(self.bwid)
        retu popup_filter_menu(a:wid, empty(self.res) ? "\<C-c>" : a:key)
    elseif a:key is# "\<C-d>" || a:key is# "\<C-u>"
        cal popup_setoptions(self.pwid, #{zindex: 100})
        cal popup_setoptions(self.ewid, #{zindex: 99})
        cal popup_setoptions(self.rwid, #{zindex: 98})
        cal feedkeys(a:key)
    else
        cal popup_setoptions(self.ewid, #{zindex: 100})
        cal popup_setoptions(self.rwid, #{zindex: 99})
        cal popup_setoptions(self.pwid, #{zindex: 98})
        cal feedkeys(a:key)
    endif
    retu 1
endf

" scroll preview
fu! s:fzsearch.pv(_, wid, key) abort
    if a:key is# "\<Esc>"
        cal popup_close(self.bwid)
        cal popup_close(self.pwid)
        cal popup_close(self.ewid)
        cal feedkeys("\<C-c>")
    elseif a:key is# "\<C-d>"
        cal self.scroll(a:wid, 1)
    elseif a:key is# "\<C-u>"
        cal self.scroll(a:wid, 0)
    elseif a:key is# "\<C-n>" || a:key is# "\<C-p>" || a:key is# "\<CR>"
        cal popup_setoptions(self.rwid, #{zindex: 100})
        cal popup_setoptions(self.ewid, #{zindex: 99})
        cal popup_setoptions(self.pwid, #{zindex: 98})
        cal feedkeys(a:key)
    else
        cal popup_setoptions(self.ewid, #{zindex: 100})
        cal popup_setoptions(self.rwid, #{zindex: 99})
        cal popup_setoptions(self.pwid, #{zindex: 98})
        cal feedkeys(a:key)
    endif
    retu 1
endf

" =====================
fu! s:fzf_histories()
    cal s:fzsearch.popup(#{title: 'Histories', list: execute('ol')->split('\n')->map({_,v -> split(v, ': ')[1]}),
        \ type: 'f', eprfx: '['.substitute(getcwd(), $HOME, '~', 'g').']>>'})
endf

fu! s:fzf_buffers()
    cal s:fzsearch.popup(#{title: 'Buffers', list: execute('ls')->split('\n')->map({_,v -> split(v, '"')[1]})
            \ ->filter({_,v -> v != '[No Name]' && v != '[無名]' && v !~ '!.*'}),
        \ type: 'f', eprfx: '['.substitute(getcwd(), $HOME, '~', 'g').']>>'})
endf
" =====================

aug FzColor
    au!
    au ColorScheme * hi FzMatch cterm=BOLD cterm=underline ctermfg=196 ctermbg=237
    au ColorScheme * hi FzWin ctermfg=114 ctermbg=237
    au ColorScheme * hi FzBWin cterm=BOLD ctermfg=145 ctermbg=238
    au ColorScheme * hi FzEWin ctermfg=39 ctermbg=237
aug END

noremap <silent><Plug>(fzf-histories) :<C-u>cal <SID>fzf_histories()<CR>
noremap <silent><Plug>(fzf-buffers) :<C-u>cal <SID>fzf_buffers()<CR>
" }}}
```
</div></details>

### [junegunn/fzf](https://github.com/junegunn/fzf)
作者同様のGo製のコマンドですね。
こんな神コマンドを真似なんて本来できないんですが、gitレポじゃなくて`git ls-files`が使えない場合にファイラが開けないのは致命的なので
`find`コマンドでどうにか似たような挙動を再現しました。（使えなくはないレベル。）

やっていることは、find結果を1階層ごとに非同期プロセスで返し次第、`fzf.vim`のポップアップにわたす感じです。

<details><summary>プログラム</summary><div>

```vim
" ===================================================================
" junegunn/fzf
" ===================================================================
" {{{
let s:fzf = #{cache: [], maxdepth: 4, gcache: [],
    \ not_path_arr: [
         \'"*/.**/*"',
         \'"*node_modules/*"', '"*target/*"'
         \'"*Applications/*"', '"*AppData/*"', '"*Library/*"',
         \'"*Music/*"', '"*Pictures/*"', '"*Movies/*"', '"*Videos/*"'
         \'"*OneDrive/*"',
    \ ],
\}

" TODO fzf delete
fu! TestFzfMaxDepth(n) abort
    let s:fzf.maxdepth = a:n
endf
" TODO fzf maxdepth かえるコマンド作るか

let s:fzf.postfix = ' -type f -not -path '.join(s:fzf.not_path_arr, ' -not -path ')
let s:fzf.searched = getcwd()
let s:fzf.get_gitls = {-> system('git ls-files -c')->split('\n')->filter({_,v->!empty(v)}) }
let s:fzf.get_file_d1 = {-> system('find . -mindepth 1 -maxdepth 1'.s:fzf.postfix)->split('\n')->filter({_,v->!empty(v)}) }
let s:fzf.get_file = { v -> 'find . -mindepth '.v.' -maxdepth '.v.s:fzf.postfix }

fu! s:fzf.files() abort
    let pwd = getcwd()
    let chk = system('git status')
    let self.is_git = v:shell_error ? 0 : 1

    " moved or first
    if stridx(pwd, self.searched) == -1
                "\ || (self.is_git && empty(self.gcache))
                \ || (self.is_git)
                \ || (!self.is_git && empty(self.cache))
        let self.searched = pwd

        if self.is_git
            " TODO fzf no need cache?
            let self.gcache = self.get_gitls()
            cal s:echoI('cache: git ls-files -c')
        else
            let self.cache = self.get_file_d1()
            cal self.asyncfind()
        endif
    endif

    cal s:fzsearch.popup(#{title: self.is_git ? 'Project Files' : 'Files',
        \ list: self.is_git ? self.gcache : self.cache,
        \ type: 'f', eprfx: '['.substitute(pwd, $HOME, '~', 'g').']>>'})
endf

fu! s:fzf.asyncfind() abort
    cal s:runcat.start()
    let self.notwid = popup_notification('find files in ['.s:fzf.searched.'] and caching ...', #{zindex: 51, line: &lines, col: 5})
    let self.jobcnt = self.maxdepth-1
    let self.endjobcnt = 0
    for depth in range(2, self.maxdepth)
        cal job_start(self.get_file(depth), #{out_cb: self.asyncfind_start, close_cb: self.asyncfind_end})
    endfor
endf

fu! s:fzf.asyncfind_start(ch, msg) abort
    cal add(self.cache, a:msg)
endf

fu! s:fzf.asyncfind_end(ch) abort
    let s:fzsearch.list = self.cache
    cal s:fzsearch.list_upd()

    let self.endjobcnt += 1
    if self.endjobcnt == self.jobcnt
        cal s:runcat.stop()
        cal popup_close(self.notwid)
        cal popup_notification('find files cached !', #{zindex: 51, line: &lines, col: 5})
    endif
endf

fu! s:fzfexe() abort
    cal s:fzf.files()
endf
noremap <silent><Plug>(fzf-smartfiles) :<C-u>cal <SID>fzfexe()<CR>
" }}}
```
</div></details>

余談ですが、fzf以外にも似たようにalternativeコマンドとしてRust製の
- ls -> [exa](https://github.com/ogham/exa)
- cd -> [zoxide](https://github.com/ajeetdsouza/zoxide)
- cat -> [bat](https://github.com/sharkdp/bat)
- find -> [fd](https://github.com/sharkdp/fd)
- grep -> [rg](https://github.com/BurntSushi/ripgrep) (ripgrep)
- sed -> [sd](https://github.com/chmln/sd)
- ps, top -> [proc](https://github.com/dalance/procs)
- git -> Go製の[lazygit](https://github.com/jesseduffield/lazygit)
- docker -> Go製の[lazydocker](https://github.com/jesseduffield/lazydocker)

を使ってます。vimとかじゃなくて普通にCLIで意味わかんないほど便利なのでおすすめ。
他にも[GithubCLI](https://docs.github.com/ja/github-cli/github-cli/about-github-cli)や[QiitaCLI](https://github.com/increments/qiita-cli)、テーマの[Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh)や[starship](https://github.com/starship/starship)などがおすすめです。
以下の記事の`#cli-tools`セクションに良くまとまっているのでどうぞ。


https://qiita.com/ucan-lab/items/c1a12c20c878d6fb1e21#cli-tools

Rust製ツールは以下にまとまってます。

https://wonderwall.hatenablog.com/entry/rust-command-line-tools


### [easymotion/vim-easymotion](https://github.com/easymotion/vim-easymotion)
いわずと知れた神プラグインですが、100行(ほど)で再現しました。
が、WindowsのGitBashだとバッファを表示するのがすごく重いのか
easymotion描画ようのダミーバッファを表示するだけで2秒ほどかかり、なんにも思考のスピードじゃなかったので
ダミーバッファでなく、本ファイルのバッファに無理やり描画してundoするとかいう強引技を使ってました。
<details><summary>プログラム</summary><div>

```vim
" ===================================================================
" easymotion/vim-easymotion
" ===================================================================
" {{{
" m, g read some function doesn't work just as I want
let s:emotion = #{keypos: [], klen: 1, keys: ['s', 'w', 'a', 'd', 'j', 'k', 'h', 'l'], popid: 0}

fu! s:emotion.exe() abort
    " fold all open
    norm zR
    " get target chars in current window without empty line
    " [{'row': row number, 'col': [ col number, ... ]}...]
    let self.keypos = []
    let self.klen = 1
    let wininfo = []
    let tarcnt = 0
    let rn = line('w0')
    let self.cl = line('.')
    let self.cc = col('.')
    let self.sl = line('w0')
    let self.ctx = getline('w0', 'w$')
    for l in self.ctx
        " loop row without 'including MultiByte' and 'empty', get head chars
        " 日本語は1文字でマルチバイト3文字分だが、カーソル幅は2なのでめんどい、日本語を含む行は弾く
        if l !~ '^[ -~|\t]\+$'
            let rn+=1
            continue
        endif
        let chars = []
        let ofst = 0
        while ofst != -1
            let st = matchstrpos(l, '\<.', ofst)
            let ofst = matchstrpos(l, '.\>', ofst)[2]
            if st[0] != ''
                cal add(chars, st[2])
            endif
        endwhile
        if !empty(chars)
            cal add(wininfo, #{row: rn, col: chars})
        endif
        let tarcnt = tarcnt+len(chars)
        let rn+=1
    endfor
    if tarcnt==0
        retu
    endif
    " calc key stroke length, keyOrder is 'ssw' = [0,0,1]
    while tarcnt > pow(len(self.keys), self.klen)
        let self.klen+=1
    endwhile
    let keyOrder = range(1, self.klen)->map({->0})
    " sort near current line, create 'self_keypos' map like this
    " [{'row': 1000, 'col': [{'key': 'ssw', 'pos': 7}, ... ]}, ... ]
    for r in sort(deepcopy(wininfo), { x,y -> abs(x.row-self.cl) - abs(y.row-self.cl) })
        let tmp = []
        for col in r.col
            cal add(tmp, #{key: copy(keyOrder)->map({i,v->self.keys[v]})->join(''), pos: col})
            let keyOrder = self.incrementNOrder(len(self.keys)-1, keyOrder)
        endfor
        cal add(self.keypos, #{row: r.row, col: tmp})
    endfor
    " create preview window
    sil! e 'emotion'
    setl buftype=nofile bufhidden=wipe nobuflisted
    " fill blank
    cal setline(1, range(1, self.sl))
    cal self.previewini()
    " disable diagnostic
    if exists('*CocAction')
        cal CocAction('diagnosticToggle')
    endif
    " draw
    cal matchadd('EmotionBase', '.', 98)
    cal self.draw(self.keypos)
    cal popup_close(self.popid)
    let self.popid = popup_create('e-motion', #{line: &lines, col: &columns*-1, mapping: 0, filter: self.char_enter})
    cal setwinvar(self.popid, '&wincolor', 'DarkBlue')
    echo ''
endf

fu! s:emotion.previewini() abort
    " restore contents
    cal setline(self.sl, self.ctx)
    " restore cursor position
    cal cursor(self.sl+5, self.cc)
    norm! zt
    cal cursor(self.cl, self.cc)
endf

" function: increment N order
" 配列をN進法とみなし、1増やす. 使うキーがssf → sws と繰り上がる仕組み
fu! s:emotion.incrementNOrder(nOrder, keyOrder) abort
    if len(a:keyOrder) == 1
        retu [a:keyOrder[0]+1]
    endif
    let tmp = []
    let overflow = 0
    for idx in reverse(range(0, len(a:keyOrder)-1))
        " 1. increment last digit
        if idx == len(a:keyOrder)-1
            cal insert(tmp, a:keyOrder[idx] == a:nOrder ? 0 : a:keyOrder[idx]+1)
            if tmp[0] == 0
                let overflow = 1
            endif
            continue
        endif
        " 2. check next digit
        if overflow
            cal insert(tmp, a:keyOrder[idx] == a:nOrder ? 0 : a:keyOrder[idx]+1)
            let overflow = a:keyOrder[idx] == a:nOrder ? 1 : 0
        else
            cal insert(tmp, a:keyOrder[idx])
        endif
    endfor
    retu tmp
endf

" draw keystroke
" 日本語は1文字でマルチバイト3文字分だが、カーソル幅は2なのでめんどいから弾いてある
" posの次文字がマルチバイトだと、strokeが2回以上残ってる時、変に文字を書き換えてカラム数変わる
fu! s:emotion.draw(keypos) abort
    cal self.previewini()
    cal self.hl_del(['EmotionFin', 'EmotionWip'])
    let hlpos_wip = []
    let hlpos_fin = []
    for r in a:keypos
        let line = getline(r.row)
        for c in r.col
            let colidx = c.pos-1
            let view_keystroke = c.key[:0]
            let offset = colidx-1
            cal add(hlpos_fin, [r.row, c.pos])
            if len(c.key)>=2
                let view_keystroke = c.key[:1]
                cal add(hlpos_wip, [r.row, c.pos, 2])
            endif
            let line = colidx == 0
                \ ? view_keystroke.line[len(view_keystroke):]
                \ : line[0:offset].view_keystroke.line[colidx+len(view_keystroke):]
        endfor
        cal setline(r.row, line)
    endfor
    for t in hlpos_fin
        cal matchaddpos('EmotionFin', [t], 99)
    endfor
    for t in hlpos_wip
        cal matchaddpos('EmotionWip', [t], 100)
    endfor
endf

fu! s:emotion.char_enter(winid, key) abort
    " noop (for polyglot bug adhoc)
    if strtrans(a:key) == "<80><fd>`"
        retu 1
    endif
    " only accept defined emotion key
    if self.keys->index(a:key) == -1
        " go out e-motion
        cal popup_close(self.popid)
        let p = getpos('.')
        " close preview
        b #
        cal cursor(p[1],p[2])
        cal self.hl_del(['EmotionFin', 'EmotionWip', 'EmotionBase'])
        " restore diagnostic
        if exists('*CocAction')
            cal CocAction('diagnosticToggle')
        endif
        cal s:echoE('e-motion: go out')
        retu 1
    endif
    " upd emotion.keypos
    let tmp = self.keypos->deepcopy()->map({ _,r -> #{row: r.row,
        \ col: r.col->filter({_,v->v.key[0]==a:key})->map({_,v->#{key: v.key[1:], pos: v.pos}})} })
        \ ->filter({_,v->!empty(v.col)})
    " nomatch -> noop
    if empty(tmp)
        retu 1
    else
        let self.keypos = tmp
    endif
    " if last match -> end e-motion
    if len(self.keypos) == 1 && len(self.keypos[0].col) == 1
        cal popup_close(self.popid)
        " close previeew
        b #
        norm! zR
        cal cursor(self.keypos[0].row, self.keypos[0].col[0].pos)
        cal self.hl_del(['EmotionFin', 'EmotionWip', 'EmotionBase'])
        " restore diagnostic
        if exists('*CocAction')
            cal CocAction('diagnosticToggle')
        endif
        cal s:echoI('e-motion: finish')
        retu 1
    endif
    " redraw
    cal self.draw(self.keypos)
    retu 1
endf

" about highlight setting
fu! s:emotion.hl_del(group_name_list) abort
    cal getmatches()->filter({ _,v -> match(a:group_name_list, v.group) != -1 })->map({ _,v -> matchdelete(v.id) })
endf

aug emotion_hl
    au!
    au ColorScheme * hi EmotionBase ctermfg=59
    au ColorScheme * hi EmotionWip ctermfg=166 cterm=bold
    au ColorScheme * hi EmotionFin ctermfg=196 cterm=bold
aug END

fu! s:emotion() abort
    cal s:emotion.exe()
endf
noremap <silent><Plug>(emotion) :<C-u>cal <SID>emotion()<CR>
" }}}

```
</div></details>

まぁでもこの神プラグインを簡易的とはいえvimrcに入れ込めたのは結果気持ちよかったです。

### [mhinz/vim-startify](https://github.com/mhinz/vim-startify)
どうしてもスタート画面に『[ぼっちざろっく！](https://bocchi.rocks/)』を入れたくて、セッション管理以外の部分を真似しました。
ついでにチートシートも作成して、[vim-which-key](https://github.com/liuchengxu/vim-which-key)の劣化版みたいなこともしました。
<details><summary>プログラム</summary><div>

```vim
" ===================================================================
" mhinz/vim-startify
" ===================================================================
" {{{
let s:start = #{}

" ぼっちざろっく{{{
let s:start.btr_logo = [
    \'                                                                                                                                              dN',
    \'                                                                                                              ..                             JMF',
    \'                                                                                                         ..gMMMM%                           JMF',
    \'                                                                                                       .MM9^ .MF                           (MF',
    \'                                                                                              .(,      ("   .M#                  .g,      .MF',
    \'                                        .,  dN                                             gg,,M@          .M#                  .M#!      (M>',
    \'                                        JM} M#             .MNgg.                     .g,  ?M[ 7B         .MMg+gg,.           .MM"        ."',
    \'                                ...gNMN,.Mb MN           .gMM9!                      .(MN,  .=           .MMM9=  ?MN,         (WN,      .MM ',
    \'                   jN-      ..gMMN#!     (Mp(M}       .+MMYMF                    ..kMMWM%               ,M#^       dN.          .WNJ,   JM',
    \'                   MM     .MM9^  dN,                 dNB^ (M%                   ?M"!  ,M\        .,               .M#   .&MMMN,    ?"   M#',
    \'                   MN            .MN#^                    dM:  ..(J-,                 ,B         .TM             .M#   ,M@  .MF',
    \'                   MN.       ..MMBMN_                     dN_.MM@"!?MN.   TMm     .a,                           (M@         MM^',
    \'                   MN.     .MM"  JMb....       ..        dMMM=     .Mb            ?HNgJ..,                   .MM^',
    \'                   dM{          -MMM#7"T""   .dN#TMo       ?      .MM^                 ?!                 +gM#=',
    \'                   (M]         .MN(N#       .M@  .MF              .MM^                                      ~',
    \'                   .MN          ?"""             MM!            .MMD',
    \'                    ?N[                                         7"',
    \'                     TMe',
    \'                      ?MN,',
    \'                        TMNg,'
    \]
"}}}

" cheat sheet {{{
let s:start.cheat_sheet_win = [
    \'       ╭── Window ──────────────────────────────────────────╮           ╭── Search ───────────────────────────────────────╮',
    \'       │ C-n / p    | (buffer tab)(next / prev)             │           │ Space e    | (explorer)                         │',
    \'       │ Space x    | (buffer tab)(close)                   │           │ Space f    | (fzf)(files / projectfiles auto)   │',
    \'       │ C-w v / s  | (window split)(vertical / horizontal) │           │ Space h    | (fzf)(histories)                   │',
    \'       │ ←↑↓→       | (window)(resize)                      │           │ Space b    | (fzf)(buffers)                     │',
    \'       │ C-hjkl     | (window)(forcus)                      │           │ Space m    | (marks)                            │',
    \'       │ Space t/g  | (terminal)/(lazygit)                  │           │ Space s    | (fuzzy search in file / quickfix)  │',
    \'       │ Space z    | (Zen Mode)                            │           │ Space*2 s  | (grep interactive)                 │',
    \'       ╰────────────────────────────────────────────────────╯           │ Space q    | (clear search highlight)           │',
    \'                                                                        ╰─────────────────────────────────────────────────╯',
    \'',
    \'       ╭── Motion ───────────────────────────────────────────╮          ╭── Command ──────────────────────────────────────────╮',
    \'       │ Space v       | (IDE Action Menu)                   │          │ :PlugInstall            | (plugins install)         │',
    \'       │ Space w       | (f-scope toggle)                    │          │ :PlugUnInstall          | (plugins uninstall)       │',
    \'       │ Tab S-Tab     | (jump 5rows)                        │          │ :RunCat [option]        | (running cat)             │',
    \'       │ s             | (easymotion)                        │          │ :RunCatStop             | (running cat stop)        │',
    \'       │ mm            | (mark toggle)                       │          │ :TrainingWheelsProtocol | [option] (training vim)   │',
    \'       │ mn / mp       | (mark next / prev)                  │          ╰─────────────────────────────────────────────────────╯',
    \'       │ mc / mx       | (mark clear file / delete all file) │',
    \'       │ INSERT C-hjkl | (cursor move)                       │',
    \'       │ VISUAL C-jk   | (blok up / down)                    │',
    \'       ╰─────────────────────────────────────────────────────╯',
    \]
" }}}

fu! s:start.exe() abort
    let fopen = execute('ls')->split('\n')->map({_,v -> split(v, '"')[1]})
            \ ->filter({_,v -> v != '[No Name]' && v != '[無名]'})->len()
    if fopen
        cal self.move()
        retu
    endif
    " preview window
    sil! exe 'e _cheat_cheet_'
    setl buftype=nofile bufhidden=wipe nobuflisted modifiable
    setl nonumber norelativenumber nocursorline nocursorcolumn signcolumn=no
    let &filetype = 'Bocchi_The_Rock'
    nmap <buffer>i \<Esc>
    nmap <buffer>I \<Esc>
    nmap <buffer>a \<Esc>
    nmap <buffer>A \<Esc>
    nmap <buffer>s \<Esc>

    " draw
    cal append('$', self.btr_logo)
    cal append('$', ['',''])
    cal append('$', self.cheat_sheet_win)
    hi BTR ctermfg=218 cterm=bold
    cal matchaddpos('BTR', range(2,9)->map({_,v->[v]}), 999)
    cal matchaddpos('BTR', range(10,17)->map({_,v->[v]}), 999)
    cal matchaddpos('BTR', range(18,21)->map({_,v->[v]}), 999)
    cal matchadd('User_greenfg_blackbg', '[─│╰╯╭╮]', 20)
    cal matchadd('DarkOrange', '\(Window\|Search\|Motion\|Command\)')
    cal matchadd('DarkBlue', '│.\{-,25}|', 19)
    cal matchadd('DarkRed', '(.*)', 18)

    " fix
    setl nomodifiable nomodified
endf

fu! s:start.move() abort
    cal clearmatches()
    cal s:fmode.activate()

    " deactivate
    aug start_vim
        au!
    aug END
endf

" only first call
aug start_vim
    au!
    au VimEnter * cal s:start.exe()
    au BufReadPre * cal s:start.move()
aug END
" }}}

```
</div></details>

### [joshdick/onedark.vim](https://github.com/joshdick/onedark.vim)
カラースキームです。私が人生で初めて使ったエディタが、サ終してしまった[Atom](https://atom.softonic.jp/mac)でして、どうしても`onedark`が好きでずっと使っています。
ちなみに[復活](https://qiita.com/access3151fq/items/6fd35685510aa5f954e6)してるらしい。

ちなみに中身はほぼコピペです。。。
<details><summary>プログラムの一部スクショ</summary><div>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453310/e85f487f-634e-0e4a-e320-205a902db23b.png)

</div></details>

### Popup terminal/git

<details><summary>ソースの画像</summary><div>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453310/ca1988f4-4809-bca4-522e-c8fbed8d7c54.png)
</div></details>

コンパイルエラーを放置してます！
LSPの`coc-vimlsp`を使っているのですが
`E125: Illegal argument: hidden:1 (vimlsp)`と怒られてるのに無視してます。
あと途中からメンテが面倒になって適当になっています、、
最悪ですね。


### Completion

補完系プラグインの挙動を真似したかったのですが、とりあえず全範囲をソースで補完だ！としたところ
一文字打つたびに重たい検索が走り出すもっさりエディタになりました。
補完はかなりシビアに非同期処理の遅延とかをいじった方がいいので、流石に既存プラグイン使った方がいいです。
あと正規表現機能とかぶってエラーになるの放置してました。。

<details><summary>プログラム</summary><div>

```vim
" completion {{{
" TODO completion リファクタ
" TODO completion ~の後ろで正規表現やりに行っちゃう。=は大丈夫
""let s:completion = #{exclude: [" ()[]{}<>'`".'"'], confirmed: 0, done: {-> execute('let s:completion.confirmed = 1')}}
let s:completion = #{exclude: [" ~()[]{}<>'`".'"'], opened: 0, confirmed: 0}

fu! s:completion.exe() abort
    if self.confirmed
        let self.confirmed = 0
        let self.opend = 0
        retu
    endif
    if col('.') == 1 || match(self.exclude, getline('.')[col('.')-2]) != -1
        retu
    endif
    if !pumvisible()
        " 開いてたはずだが、確定されずに閉じてるなら何もしない
        if self.opened
            retu
        endif
        let self.opened = 1
        cal feedkeys("\<C-n>")
    endif
endf

fu! s:completion.done() abort
    let self.confirmed = 1
    let self.opened = 0
endf

" TODO completion <BS>のあとに補完だしたくない
if glob('~/.vim/pack/plugins/start/coc.nvim') == ''
    au TextChangedI,TextChangedP * cal s:completion.exe()
    au CompleteDone * cal s:completion.done()
endif
" }}}

```
</div></details>



# 現在のvimrc
さて、模倣した3000行バージョンはそれとしてとっておき
現在私は既存プラグインにしっかり頼った版を使っています。
自作したいものは、ちゃんとプラグインを自作して、入れて使っているので
vimrc自体はとてもスッキリしました。

https://github.com/serna37/dotfiles

プラグイン部分はこうなってます。46件入れています。
```vim
call plug#begin()

" ### Enhanced vim motion
Plug 'serna37/vim-modern-basic'
Plug 'serna37/vim-anchor5'
Plug 'easymotion/vim-easymotion'
Plug 'haya14busa/vim-edgemotion'
Plug 'serna37/edgemotion-vertical'
Plug 'rhysd/clever-f.vim'
Plug 'serna37/vim-fscope-around'
Plug 't9md/vim-quickhl'
Plug 'haya14busa/vim-asterisk'
Plug 'MattesGroeger/vim-bookmarks'
Plug 'simeji/winresizer'
Plug 'yuttie/comfortable-motion.vim'
let g:EasyMotion_do_mapping = 0
nnoremap s <Plug>(easymotion-sn)
nnoremap <Leader><Leader>w <Plug>(easymotion-bd-w)
nnoremap <C-j> <Plug>(edgemotion-j)<Plug>(anchor)
nnoremap <C-k> <Plug>(edgemotion-k)<Plug>(anchor)
let g:clever_f_smart_case = 1
let g:clever_f_timeout_ms = 2000
let g:clever_f_highlight_timeout_ms = 2000
aug cleaver_f
    au!
    au ColorScheme * hi CleverFDefaultLabel cterm=BOLD,underline ctermfg=40 ctermbg=0
aug END
nnoremap <leader>w <Plug>(fscope-around-toggle)<Plug>(clever-f-reset)
nnoremap # <Plug>(asterisk-z*)<Plug>(quickhl-manual-this)
nnoremap <silent><Leader>q <Plug>(quickhl-manual-reset)<Plug>(clever-f-reset):noh<CR>
let g:comfortable_motion_no_default_key_mappings = 1
let g:comfortable_motion_interval = 1000.0 / 60
let g:comfortable_motion_friction = 70.0
let g:comfortable_motion_air_drag = 5.0
nnoremap <silent><C-f> :cal comfortable_motion#flick(200)<CR>
nnoremap <silent><C-b> :cal comfortable_motion#flick(-200)<CR>

" ### Enhanced Visualization
Plug 'mhinz/vim-startify'
Plug 'vim-airline/vim-airline'
Plug 'vim-airline/vim-airline-themes'
Plug 'sheerun/vim-polyglot'
Plug 'joshdick/onedark.vim'
Plug 'junegunn/goyo.vim'
Plug 'liuchengxu/vim-which-key'
let g:airline_theme = 'onedark'
let g:airline#extensions#tabline#enabled = 1
let g:airline_powerline_fonts = 1
let g:airline_highlighting_cache = 1
fu! s:moveBuf(flg) abort
    let current_id = ''
    let buf_arr = []
    for v in split(execute('ls'), '\n')->map({ _,v -> split(v, ' ')})
        let x = copy(v)->filter({ _,v -> !empty(v) })
        if stridx(x[1], 'F') == -1 && stridx(x[1], 'R') == -1
            cal add(buf_arr, x[0])
            if stridx(x[1], '%') != -1
                let current_id = x[0]
            endif
        endif
    endfor
    let buf_idx = a:flg == 'next' ? match(buf_arr, current_id) + 1 : match(buf_arr, current_id) - 1
    let buf_id = buf_idx == len(buf_arr) ? buf_arr[0] : buf_arr[buf_idx]
    exe 'b '.buf_id
endf

fu! s:closeBuf() abort
    let now_b = bufnr('%')
    cal s:moveBuf('prev')
    execute('bd ' . now_b)
endf

noremap <silent><Plug>(buf-prev) :<C-u>cal <SID>moveBuf('prev')<CR>
noremap <silent><Plug>(buf-next) :<C-u>cal <SID>moveBuf('next')<CR>
noremap <silent><Plug>(buf-close) :<C-u>cal <SID>closeBuf()<CR>
nnoremap <C-n> <Plug>(buf-prev)
nnoremap <C-p> <Plug>(buf-next)
nnoremap <Leader>x <Plug>(buf-close)
nnoremap <silent><Leader>z :Goyo<CR>
nnoremap <silent><Leader> :WhichKey '<Space>'<CR>
set timeoutlen=500

" ### Filer
Plug 'junegunn/fzf', {'do': { -> fzf#install() }}
Plug 'junegunn/fzf.vim'
let $FZF_PREVIEW_PREVIEW_BAT_THEME = 'TwoDark'
nnoremap <silent><Leader>f :cal execute('CocCommand fzf-preview.'.(system('git rev-parse --is-inside-work-tree') =~ 'fatal'?'DirectoryFiles':'ProjectFiles'))<CR>
nnoremap <silent><Leader>b :CocCommand fzf-preview.Buffers<CR>
nnoremap <silent><Leader>hf :CocCommand fzf-preview.MruFiles<CR>
nnoremap <silent><Leader>e :CocCommand explorer --width 30<CR>
nnoremap <silent><Leader>s :CocCommand fzf-preview.Lines<CR>
nnoremap <silent><Leader><Leader>s :CocCommand fzf-preview.ProjectGrep .<CR>
nnoremap <silent><Leader>m :CocCommand fzf-preview.Bookmarks<CR>
nnoremap <silent><Leader>nn :CocCommand fzf-preview.MemoList<CR>
nnoremap <silent><Leader>ng :CocCommand fzf-preview.MemoListGrep .<CR>
au DirChanged * cal execute('CocCommand explorer --no-focus --width 30')

" ### Git
Plug 'tpope/vim-fugitive'
Plug 'airblade/vim-gitgutter'

" ### Reading
Plug 'junegunn/rainbow_parentheses.vim'
Plug 'andymass/vim-matchup'
Plug 'preservim/vim-indent-guides'
Plug 'liuchengxu/vista.vim'
Plug 'wfxr/minimap.vim'
au VimEnter * RainbowParentheses
let g:indent_guides_enable_on_vim_startup = 1
let g:indent_guides_guide_size = 1
let g:indent_guides_start_level = 2
let g:indent_guides_exclude_filetypes = ['help', 'coc-explorer', 'startify']
let g:indent_guides_auto_colors = 0
aug indent_guide
    au!
    au ColorScheme * hi IndentGuidesOdd ctermbg=236
    au ColorScheme * hi IndentGuidesEven ctermbg=234
aug END
let g:vista_sidebar_width = 15
nnoremap <silent><Leader><Leader>o :Vista!!<CR>
let g:minimap_git_colors = 1
nnoremap <silent><Leader><Leader>m :MinimapToggle<CR>

" ### Writing
Plug 'SirVer/ultisnips'
Plug 'markonm/traces.vim'
Plug 'scrooloose/nerdcommenter'
Plug 'lfilho/cosco.vim'
Plug 'matze/vim-move'
Plug 'jiangmiao/auto-pairs'
Plug 'tpope/vim-surround'
Plug 'tpope/vim-repeat'
let g:UltiSnipsExpandTrigger="<C-s>"
" o -> A+CR (adhoc for snippet tabstop bug...)
nnoremap o A<CR>
let g:cosco_filetype_whitelist = ['cpp', 'rust']
" return normal & save
inoremap jj <Esc>:CommaOrSemiColon<CR>:w<CR>
let g:move_key_modifier_visualmode = 'C'
let g:AutoPairsMapCh = 0

" ### LSP IDE
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'thinca/vim-quickrun'
Plug 'puremourning/vimspector'
Plug 'serna37/vim-IDE-menu'
let g:coc_global_extensions = ['coc-explorer', 'coc-snippets', 'coc-fzf-preview',
            \ 'coc-sh', 'coc-vimlsp', 'coc-json', 'coc-sql',
            \ 'coc-html', 'coc-css', 'coc-tsserver',
            \ 'coc-clangd', 'coc-rust-analyzer', 'coc-go',
            \ 'coc-pyright', 'coc-java',
            \ ]
let g:coc_snippet_next = '<Tab>'
let g:coc_snippet_prev = '<S-Tab>'
au CursorHold * sil cal CocActionAsync('highlight')
inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm() : "\<CR>"
inoremap <silent><expr> <Tab> coc#pum#visible() ? coc#pum#next(1) : "\<Tab>"
inoremap <silent><expr> <S-Tab> coc#pum#visible() ? coc#pum#prev(1) : "\<S-Tab>"
nnoremap <Leader>d <Plug>(coc-definition)
nnoremap <silent><Leader>r :CocCommand fzf-preview.CocReferences<CR>
nnoremap <silent><Leader>o :CocCommand fzf-preview.CocOutline<CR>
nnoremap <silent><Leader>? :cal CocAction('doHover')<CR>
nnoremap <Leader>, <plug>(coc-diagnostic-next)
nnoremap <Leader>. <plug>(coc-diagnostic-prev)
nnoremap <silent><nowait><expr> <C-d> coc#float#has_scroll() ? coc#float#scroll(1) : comfortable_motion#flick(100)
nnoremap <silent><nowait><expr> <C-u> coc#float#has_scroll() ? coc#float#scroll(0) : comfortable_motion#flick(-100)

" ### util
Plug 'serna37/vim-tutorial'
Plug 'soywod/unfog.vim'
Plug 'glidenote/memolist.vim'
Plug 'segeljakt/vim-silicon'
Plug 'nanotee/zoxide.vim'
Plug 'serna37/vim-atcoder-menu'

call plug#end()
```

# その他のお気に入りプラグイン
全部気に入ってますが、せっかくなので紹介していないものも含めて
特に気に入っているプラグインをピックアップします。

### 移動系まとめ
- [serna37/vim-anchor5](https://github.com/serna37/vim-anchor5): 5行移動、前後にマーカー
- [haya14busa/vim-edgemotion](https://github.com/haya14busa/vim-edgemotion): 縦方向に、行があるとこまで一気にジャンプ
- [serna37/edgemotion-vertical](https://github.com/serna37/edgemotion-vertical): 横方向版(0 ^ $とかわらんがedgemotionと似たキーで飛べるので快適)
- [rhysd/clever-f.vim](https://github.com/rhysd/clever-f.vim): f移動最強
- [serna37/vim-fscope-around](https://github.com/serna37/vim-fscope-around): f移動先を一瞬で見れるように

3/5が自作で恐縮ですが、これらをまとめてガンガン移動しています。

### 見た目系
- [sheerun/vim-polyglot](https://github.com/sheerun/vim-polyglot)
ほぼ全部の言語のシンタックスハイライトがあります。これだけ入れればOK

### Reading
- [junegunn/rainbow_parentheses.vim](https://github.com/junegunn/rainbow_parentheses.vim): 対応カッコを光らせる。綺麗
- [andymass/vim-matchup](https://github.com/andymass/vim-matchup): 対応文字を光らせる。`if fi`とか
- [preservim/vim-indent-guides](https://github.com/preservim/vim-indent-guides): インデントに縦線、偶奇で色変えられるので見やすい
- [liuchengxu/vista.vim](https://github.com/liuchengxu/vista.vim): メンバの階層一覧を表示、VSCodeみたい
- [wfxr/minimap.vim](https://github.com/wfxr/minimap.vim): ミニマップだす、ミニマップでスクロールもgit差分色もつく、まじVSCode


### Writing
- [SirVer/ultisnips](https://github.com/SirVer/ultisnips): 最強スニペット
- [markonm/traces.vim](https://github.com/markonm/traces.vim): 置換プレビュー
- [lfilho/cosco.vim](https://github.com/lfilho/cosco.vim): 行末の`;`を自動で
- [matze/vim-move](https://github.com/matze/vim-move): (矩形もok)選択した文字たちをグングン移動

### LSP系
- [neoclide/coc.nvim](https://github.com/neoclide/coc.nvim): LSPクライアント. 人によって好き付きあるが私はこれ. これ入れたらIDEになる
- [thinca/vim-quickrun](https://github.com/thinca/vim-quickrun): プログラム実行するやつ
- [puremourning/vimspector](https://github.com/puremourning/vimspector): DAPでデバッグする. vimでデバッグできないなんてことはない！VSCodeにあってvimにできないこともないんじゃないかな

### util系
- [soywod/unfog.vim](https://github.com/soywod/unfog.vim): 簡易タスクリスト
- [glidenote/memolist.vim](https://github.com/glidenote/memolist.vim): とりあえずmarkdownでメモできるしfzfで探せる
- [segeljakt/vim-silicon](https://github.com/segeljakt/vim-silicon): コードをスクショに変換できる、面白い
- [nanotee/zoxide.vim](https://github.com/nanotee/zoxide.vim): zoxideをvim内で使える

unfogコマンドやrustの依存が必要ですが、気に入ってます。

# 最終的に吐き出されたもの
既存に置き換える以外で吐き出したプラグイン7つがこちら。

https://github.com/serna37/vim-modern-basic

https://github.com/serna37/vim-anchor5

https://github.com/serna37/vim-fscope-around

https://github.com/serna37/vim-IDE-menu

https://github.com/serna37/vim-atcoder-menu

https://github.com/serna37/vim-tutorial

https://github.com/serna37/edgemotion-vertical

自分用カスタムをまぁまぁ汎用化したつもりなので、vimrcから設定を上書きできるようにも作りました。
一部READMEが古かったりしますが

# 最後に
今回はあえて1枚に納めてポータビリティを高めたい、(vim-plugのように)`curl`1行でインストールしたい等の目的があったとはいえ
大掃除後のvimはやっぱり世界が違いました。

皆さんも、既存のものを作り直すことなく、長すぎるコードを書くことなく
適切な粒度で開発していきましょう！（戒め）