" ========================================
" プラグインのインストール
" ========================================

call plug#begin('~/.vim/plugged')

" カラースキーム
Plug 'eihigh/vim-aomi-grayscale'
Plug 'chriskempson/base16-vim'
Plug 'cocopon/iceberg.vim'

" モーション / オペレータ
Plug 'haya14busa/vim-edgemotion'
Plug 'haya14busa/vim-asterisk'
Plug 'rhysd/clever-f.vim'
Plug 'kana/vim-textobj-user'
Plug 'sgur/vim-textobj-parameter'
Plug 'kana/vim-textobj-indent'
Plug 'hrsh7th/vim-searchx'

" テキスト編集
Plug 'tyru/caw.vim'
Plug 'machakann/vim-sandwich'
Plug 'eihigh/vim-lexiv'

" ファイラ / セレクタ
Plug 'mattn/vim-findroot'
Plug 'cocopon/vaffle.vim'
Plug 'ctrlpvim/ctrlp.vim'
Plug 'tacahiroy/ctrlp-funky'
Plug 'yssl/QFEnter'

" パッシブ拡張
" Plug 'wellle/context.vim'
Plug 'obcat/vim-sclow'

" 言語
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'
Plug 'mattn/ctrlp-lsp'
Plug 'fatih/vim-go'

" ウィンドウ
Plug 't9md/vim-choosewin'

" 小回り
Plug 'vim-jp/vimdoc-ja'
Plug 'Shougo/junkfile.vim'

" ========================================
" 標準のキーマップの上書き・調整(いくらあってもよいとされている)
" ========================================

" 検索ハイライト消去
nnoremap <silent> <Esc> :<C-u>nohlsearch<CR>

" インサート中移動で undo block を中断しない
inoremap <Left>  <C-g>U<Left>
inoremap <Right> <C-g>U<Right>

" yank 範囲
nnoremap Y y$

" クリックしてもウィンドウの位置をなるべく保つ
noremap <LeftMouse> <Cmd>call MyLeftMouse()<CR>
map <2-LeftMouse> <LeftMouse>
map <3-LeftMouse> <LeftMouse>
map <4-LeftMouse> <LeftMouse>

" Changelist
map <Plug>(cgl)  <Nop>
map           g; g;<Plug>(cgl)
map           g, g,<Plug>(cgl)
map <Plug>(cgl); g;<Plug>(cgl)
map <Plug>(cgl), g,<Plug>(cgl)

" Asterisk
map *  <Plug>(asterisk-z*)
map #  <Plug>(asterisk-z#)
map g* <Plug>(asterisk-gz*)
map g# <Plug>(asterisk-gz#)

" Lexiv
inoremap <expr> " lexiv#quote_open('"')
inoremap <expr> ' lexiv#quote_open("'")
inoremap <expr> ` lexiv#backquote_open()
inoremap <expr> { lexiv#paren_open('{')
inoremap <expr> ( lexiv#paren_open('(')
inoremap <expr> [ lexiv#paren_open('[')
inoremap <expr> } lexiv#paren_close('}')
inoremap <expr> ) lexiv#paren_close(')')
inoremap <expr> ] lexiv#paren_close(']')

" Asyncomplete
inoremap <silent> <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <silent> <expr> <Tab>   pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <silent> <expr> <CR>    (pumvisible() ? "\<C-y>" : "") . lexiv#paren_expand()

" ========================================
" 標準にはない追加のキーマップ(少ないほどよいとされている)
" ========================================

" 保存ショートカット
nnoremap <cr> :<C-u>w<CR>

" ウィンドウ管理
map - <Plug>(choosewin)
map _ <C-w>
noremap <C-w>_ :<C-u>below split<CR>
noremap <C-w>- :<C-u>above split<CR>
noremap <C-w>( :<C-u>lefta vsplit<CR>
noremap <C-w>) :<C-u>rightb vsplit<CR>
noremap <C-w>! ZZ

" ターミナルからの脱出
tnoremap <Esc><Esc> <Esc>
tnoremap <Esc>-     <C-w>:<C-u>call choosewin#start(range(1, winnr('$')))<CR>
tnoremap <Esc>n     <C-w>N
tnoremap <Esc>h     <C-w>:hide \| wincmd p<CR>

" z. でカーソル中央固定
set scrolloff=2
noremap <expr> z. &scrolloff == 99 ? ':setlocal scrolloff=2<CR>' : 'zz:setlocal scrolloff=99<CR>'

" Sandwich
let g:sandwich_no_default_key_mappings = 1
nmap sa <Plug>(sandwich-add)
xmap sa <Plug>(sandwich-add)
omap sa <Plug>(sandwich-add)
nmap sd <Plug>(sandwich-delete-auto)
xmap sd <Plug>(sandwich-delete)
nmap sr <Plug>(sandwich-replace-auto)
xmap sr <Plug>(sandwich-replace)

" Edgemotion
nmap g< <Plug>(edgemotion-k)
xmap g< <Plug>(edgemotion-k)
omap g< <Plug>(edgemotion-k)
nmap g> <Plug>(edgemotion-j)
xmap g> <Plug>(edgemotion-j)
omap g> <Plug>(edgemotion-j)

" Searchx
nnoremap l <Cmd>call searchx#start({ 'dir': 1 })<cr>
xnoremap l <Cmd>call searchx#start({ 'dir': 1 })<cr>

" これ以上のキーマップは増やしたくないので本当はコマンドパレットを使いたいが
" 仕方なく割り当てるやつ
nnoremap hh :<C-u>CtrlPMixed<CR>
nnoremap he :<C-u>e %:h<CR>
nnoremap ht :<C-u>call ReuseShellOrCreate()<CR>
nnoremap hs :<C-u>call VimGrepDir(findroot#find(), '')<Left><Left>
nnoremap hg :<C-u>vimgrep '' **<Left><Left><Left><Left>
nnoremap hr :<C-u>CtrlPMRU<CR>
nnoremap hl :<C-u>CtrlPBuffer<CR>
nnoremap hc :<C-u>botright copen<CR>

" ========================================
" 本体機能の設定
" ========================================

" エンコード
set encoding=utf-8
set fileencoding=utf-8
set fileencodings=utf-8,euc-jp,cp932

" 検索
set hlsearch
set incsearch
set ignorecase
set smartcase

" テキスト編集
set backspace=eol,indent,start
set tabstop=3
set shiftwidth=3
set smartindent

" 見た目
set nowrap
set number
set signcolumn=number
set helplang=ja
set termguicolors
set equalalways

" statusline常設
set laststatus=2

" 挙動
set autoread
set autochdir

" Abbreviation
abbrev ret return

" マウス
set mouse=n

" クリップボード
" TODO

" Esc の遅延解消
set ttimeoutlen=0

" undo 履歴
if isdirectory(expand('~/.cache/vim'))
	set undofile
	set undodir=~/.cache/vim/
endif

" grepprg
if executable('rg')
	set grepprg=rg\ --vimgrep
	set grepformat=%f:%l:%c:%m
endif

" NORMAL/INSERT でカーソル形状を変更
if &term =~ 'xterm' || &term == 'win32'
	let &t_SI = "\e[5 q"
	let &t_EI = "\e[2 q"
else
	let &t_SI = "\<Esc>]50;CursorShape=1\x7"
	let &t_EI = "\<Esc>]50;CursorShape=0\x7"
endif

" ステータスラインをシュッとさせる
" utf-8 以外の注意すべきエンコードなら強調する
function! Stl_fenc() abort
	return &fenc == "utf-8" ? "" : "[".&fenc."]"
endfunction
set statusline=\ %m%=%{Stl_fenc()}\ %<%F\ 

" ========================================
" プラグイン機能の設定
" ========================================

" CtrlP

" Searchx
let g:searchx = {}
let g:searchx.auto_accept = v:true
let g:searchx.scrolloff = &scrolloff
let g:searchx.scrolltime = 500
let g:searchx.nohlsearch = {}
let g:searchx.nohlsearch.jump = v:true
let g:searchx.markers = split(' HTSRMFGVXBDLCWZK', '.\zs')
function g:searchx.convert(input) abort
	if a:input !~# '\k'
		return '\V' .. a:input
	endif
	return a:input[0] .. substitute(a:input[1:], '\\\@<! ', '.\\{-}', 'g')
endfunction

" Lexiv
" デフォルトだと <cr> が補完確定のマッピングと
" 衝突するので、無効にする(fork 元ではできない)
let g:lexiv_no_default_key_mappings = 1

" LSP
let g:lsp_async_completion = 1
let g:lsp_document_highlight_enabled = 0
let g:lsp_diagnostics_highlights_enabled = 1
let g:lsp_diagnostics_highlights_insert_mode_enabled = 1
let g:lsp_diagnostics_highlights_delay = 100
let g:lsp_diagnostics_signs_enabled = 0
let g:lsp_document_code_action_signs_enabled = 0
let g:lsp_diagnostics_virtual_text_enabled = 1
let g:lsp_diagnostics_virtual_text_delay = 100
let g:lsp_diagnostics_virtual_text_align = "right"
let g:lsp_settings_enable_suggestions = 0

" vim-go
" LSPに寄せるのでキーマップ無効
let g:go_def_mapping_enabled = 0

" Vaffle
let g:vaffle_show_hidden_files = 1

" findroot
" 'findroot#find()' 関数目的で入れるので、発火はさせない
let g:findroot_mask = '__do_not_fire__'

" clever-f
let g:clever_f_across_no_line = 1
let g:clever_f_smart_case = 1
let g:clever_f_mark_char_color = 'Search'

" Scrollbar

" Choosewin
" overlay 表示はターミナルで壊れる
let g:choosewin_blink_on_land = 0

" ========================================
" Autocmd!
" ========================================
autocmd! User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
autocmd! VimResized * wincmd =
autocmd! WinLeave * call CurWinSaveView()
autocmd! WinScrolled * call CurWinSaveViewOnScroll(expand('<afile>'))
autocmd! WinClosed * wincmd p
autocmd! TerminalOpen * setlocal bufhidden=hide
autocmd! FileType vaffle call s:filetype_vaffle()
autocmd! FileType go call s:filetype_go()
autocmd! FileType qf call s:filetype_qf()
autocmd! QuickFixCmdPost make,grep,grepadd,vimgrep botright copen

" ========================================
" 自作関数
" ========================================

" vim-lsp
" ---------------------------------------
function! s:on_lsp_buffer_enabled() abort
	setlocal omnifunc=lsp#complete
	if exists('+tagfunc') | setlocal tagfunc=lsp#tagfunc | endif

	nmap <buffer> gd <plug>(lsp-definition)
	nmap <buffer> gD <plug>(lsp-type-definition)
	nmap <buffer> gs <plug>(lsp-document-symbol)
	nmap <buffer> gS <plug>(lsp-workspace-symbol)
	nmap <buffer> gN <plug>(lsp-previous-diagnostic)
	nmap <buffer> gn <plug>(lsp-next-diagnostic)
	nmap <buffer> gh <plug>(lsp-hover)
	nmap <buffer> gc <plug>(lsp-rename)
	nmap <buffer> gl <plug>(lsp-code-lens)
	nmap <buffer> ga <plug>(lsp-code-action)
	xnoremap <buffer> ga :LspCodeAction<CR>
	nnoremap <buffer> gq :<C-u>LspStopServer<CR>

	" gofmt & goimports
	" autocmd! BufWritePre *.go call execute('LspDocumentFormatSync') | call execute('LspCodeActionSync source.organizeImports')
endfunction

" Vaffle
" ---------------------------------------
function! s:filetype_vaffle() abort
	nmap <buffer> <Left>  <Plug>(vaffle-open-parent)
	nmap <buffer> <Right> <Plug>(vaffle-open-current)
	nmap <buffer> <End>   <Plug>(vaffle-open-current)
endfunction

" Go
" ---------------------------------------
function! s:filetype_go() abort
	" Snippet にするか迷う
	abbrev <buffer> e_ err != nil
	abbrev <buffer> a_ append

	nnoremap g.t :<C-u>GoTest<CR>
	nnoremap g.f :<C-u>GoTestFunc<CR>
endfunction

" Quickfix
" ---------------------------------------
function! s:filetype_qf() abort
	nnoremap <buffer> <CR> <CR>
	nnoremap <buffer> o :<C-u>colder<CR>
	nnoremap <buffer> O :<C-u>cnewer<CR>
endfunction

" ディレクトリ指定 vimgrep
" ---------------------------------------
function! VimGrepDir(dir, word) abort
	let esc = escape(a:word, '"')
	exe 'vimgrep "' .. esc .. '" ' .. fnameescape(a:dir) .. '/**'
endfunction

" 既存の隠れた実行中のシェルがあれば再利用、なければ作成
" ---------------------------------------
function! ReuseShellOrCreate() abort
	let bufinfo = getbufinfo()->filter({i, b ->
				\ getbufvar(b.bufnr, '&buftype') == 'terminal' &&
				\ stridx(b.name, '!'.&shell) == 0 &&
				\ b.changed == 1 &&
				\ b.hidden == 1
				\ })

	if len(bufinfo) == 0
		terminal ++curwin ++open
		return
	endif

	let bufnr = bufinfo[0].bufnr
	exe 'b '.bufnr
endfunction

" ウィンドウのカーソル位置を保存
" ---------------------------------------
let g:win_views = {}
function! CurWinSaveView() abort
	let g:win_views[winnr()] = winsaveview()
endfunction

" ウィンドウのカーソル位置を保存(スクロール時)
" ---------------------------------------
function! CurWinSaveViewOnScroll(winid) abort
	let winnr = win_id2win(a:winid)
	let g:win_views[winnr] = winsaveview()
endfunction

" ウィンドウのカーソル位置を復元(あれば)
" ---------------------------------------
function! CurWinRestView() abort
	if g:win_views->has_key(winnr())
		call winrestview(g:win_views[winnr()])
	endif
endfunction

" 上記を利用したクリックの挙動
" ---------------------------------------
function! MyLeftMouse() abort
	let prev = winnr()
	exe "normal! \<LeftMouse>"
	let curr = winnr()
	" 同じウィンドウなら通常の挙動
	if prev == curr
		return
	endif
	" 異なるウィンドウならカーソル位置を復元
	call CurWinRestView()
endfunction

" 色の切り替え
" ---------------------------------------
function! Day() abort
	set background=light
	colorscheme aomi-grayscale
	hi link SclowSbar StatusLine
	hi link EndOfBuffer Ignore
endfunction

function! Night() abort
	set background=dark
	colorscheme aomi-grayscale
	hi Normal guibg=NONE
	hi link SclowSbar StatusLine
	hi link EndOfBuffer Ignore
endfunction

call plug#end()

" 色の設定はプラグイン読み込み後に行う
call Day()