Hori Blog

フリーランスでバックエンドエンジニアとして活動している Ryota Hori のブログです。
最近はテック系記事より雑記ブログ気味。

dotfilesを刷新した(2020-09)

久々に開発環境の dotfiles を刷新したのでやったことのメモ。

きっかけ

メルカリさんの開発ライブ実況に触発された。

【解説】開発ライブ実況 #1 (Vim / Go) 編 by メルペイ Architect チーム Backend エンジニア #mercari_codecast | メルカリエンジニアリング

こういうコンテンツとっても好き。

主に改善したかったところ

  • 凝集度が低い
    • 例えば Homebrew で入れるツールが brew.sh みたいなファイルにまとまっていて、付随する環境変数などの設定は別ファイルにあって…といった感じで凝集度が低いのが気に入らなかった
      • 現にもう入れていないツールの設定などが残っていたりで良くない状態だった
  • 使えていないツールや key bind が多かった
    • ノイズになり便利で使うべきだったはずのものが埋もれてしまうので良くない

今回はすべての設定を把握している状態を目指し、定期的に掃除&覚え直しがしやすい設計を目指しました。

方針

  • shell は zsh
    • fish も使ってみたいけど文法が違うのを許容するほどのモチベーションは湧いていない
  • zsh の設定ベースは prezto
  • alias や暗黙的な挙動は使いすぎない
    • prezto の module も最低限。例えば prezto の git モジュールみたいに大量の alias が設定されてしまうのは許容するけど、実用する alias は基本的に自分で明示的に設定する。
  • Vim は Neovim
    • Shougoさんの de* 系ツールは積極利用
      • たまに互換性を失ったりバグが生じたりすることもあるけど速いし便利なので愛用している
  • grep 系ツールを ggreer/the_silver_searcher から BurntSushi/ripgrep に乗り換え。
    • 速いらしいので試しに。

やったこと

とりあえず現時点(2020-09-22)でのリポジトリがこちら。

hori-ryota/dotfiles

やってよかったことで参考になりそうなところをメモがてら紹介してみます。

凝集度を高めるためにインストール用スクリプトと設定ファイルをツールごとにまとめた

zsh/modules/ 以下に欲求ごとにディレクトリを切り、 install.shexport.zsh をそれぞれ置くことで凝集度を高めています。

インストール処理は init.sh で以下のようにすることで全ツールについて発火。

init.sh
for f in "$(dirname "${BASH_SOURCE:-$0}")/zsh/modules/"*"/install.sh"
do
  echo "Execute installation script ${f}"
  zsh -c "${f}"
  echo "Finish installation script ${f}"
done

また、 export.zsh についても zsh/modules/export.zsh で以下のように設定。

zsh/modules/export.zsh
for f in "$(dirname "${BASH_SOURCE:-$0}")/"*"/export.zsh"
do
  source "$f"
done

これを prezto の zprofile から以下のように読み込むことで一括して読み込んでいます。

zprofile
source "$HOME/.zsh/modules/export.zsh"

凝集度を高めたおかげでどこで何が設定されているのかわからないものが全くなくなり、非常にやってよかったです。

また、環境変数の設定はツールのインストール時にも必要なもの(Go 系ツールをインストールするときの $GOPATH とか)もありますが、各 install.sh から各 export.zsh を読み込むようにすることで安心して実行できるようになりました。

部分実行が気軽にできるようになったので Node.js などのバージョン更新時も対応が楽です。

export.zsh が分割されていることによる読み込みのパフォーマンスは今のところは気になっていないですが、もし気になり始めたら各 export.zsh は結合して 1 ファイルにする処理を更新時に叩くフローを検討するつもりです。

Powerlevel9k から Powerlevel10k に変えた

昔も移行しようとして設定移植が面倒でやめたような気がしていたのですが、確認したら設定を変更せずに適用できるようだったので prezto の設定を書き換え。

romkatv/powerlevel10k: A Zsh theme

問題なく動いている上、目に見えて速くなったのでやってよかったです。

vim-go をやめた

fatih/vim-go: Go development plugin for Vim

vim-go は素晴らしいツールなのですが、LSP (Language Server Protocol) である gopls が充実してきたことで必要性が薄くなったため卒業しました。

gopls

vim-go には充実した機能で長らく非常にお世話になっていたのですが、多機能なだけに依存ツールの変更への対応が荒れることもあり LSP の wrapper としての役割も含めてすべてを網羅する巨大ツールとして今後進めていくのは厳しいのかなーと見ています。

今後は、基本的な機能は LSP を活用した上で、LSP にない機能のツールについては特化した小さなプラグインやスクリプトで対応していくのが良いかなーと思っています。

とりあえず今のところは goimports, gofmt の自動発火については mattn さんの vim-goimports を使い、テストは vim-test/vim-test を使っています。

その他のツールについては vim-test でも使われている tpope/vim-dispatch で発火しています。

今のところ大きな不満はないですが、vim-test の TestFile では子パッケージまでテストを走らせてしまうためそこだけ調整しようかと思っています。

追記(2020-09-27): vim-dispatch を直接叩くようにして調整

TestNearest は引き続き vim-test のものを使っていますが、今いるパッケージのテストは vim-dispatch を直接叩くように変更しました。

ついでに golangci-lint の設定も載せておきます。

let g:dispatch_compilers['go test'] = 'go'
nnoremap <buffer> <Leader>t :<C-u>Dispatch go test %:p:h<CR>
nnoremap <buffer> <Leader>gt :<C-u>Dispatch go test %:p:h/...<CR>
nnoremap <buffer> <Leader>pt :<C-u>Dispatch go test ./...<CR>

let g:dispatch_compilers['golangci-lint run'] = 'go'
nnoremap <buffer> <Leader>l :<C-u>Dispatch golangci-lint run %:p:h<CR>
nnoremap <buffer> <Leader>gl :<C-u>Dispatch golangci-lint run %:p:h/...<CR>
nnoremap <buffer> <Leader>pl :<C-u>Dispatch golangci-lint run ./...<CR>

Neovim の Session 機能を使い始めた

Neovim には Session 機能があり、今開いている画面の構成などの状態を保存して後ほど開き直すことができます。

他のプロジェクト(リポジトリ)をちょっと確認して戻ってきたい、みたいなことが多いので、以下のような要件を満たす設定を整備してみました。

  • x-motemen/ghq で他の Git Repository を拾い、 Shougo/denite.nvim などの fuzzy finder 系ツールで検索&移動する
  • 移動時に Session 機能を使い、移動元の状態を保存する
  • 移動時に Session 機能を使い、移動先の状態を(存在したら)復元する

行って戻ってきたらさっきの状態、また行ったらさっきの状態、って感じで往復できます。便利。

Denite と ghq での設定が以下。

dein.toml
let s:data_home = empty($XDG_DATA_HOME) ? expand('~/.local/share') : $XDG_DATA_HOME
let s:session_dir = get(g:, 'denite_session_dir', s:data_home . '/nvim/denite_session')
if !isdirectory(s:session_dir)
  call mkdir(s:session_dir, 'p')
endif

function! s:session_file_name(target_dir)
  return s:session_dir . '/' . trim(substitute(resolve(a:target_dir), '/', '-', 'g'), '-') . '.vim'
endfunction

function! s:save_session(target_dir)
  execute 'mksession! ' . s:session_file_name(a:target_dir)
endfunction

function! s:load_or_create_session(target_dir)
  let s:file_name = s:session_file_name(a:target_dir)
  if filereadable(s:file_name)
    execute 'source ' . s:file_name
    return
  endif
  call s:save_session(a:target_dir)
endfunction

function! s:cdsession_action(context)
  call s:save_session(getcwd())

  let s:target = a:context['targets'][0]
  let s:path = s:target['action__path']
  execute 'cd ' . s:path
  call s:load_or_create_session(s:path)
endfunction

call denite#custom#action('directory', 'cdsession', function('s:cdsession_action'))

current directory ごとに Session を保存し、Denite での移動時に移動元の保存と移動先の復元をしています。

また、廃棄したくなることもあるので以下のような掃除処理も仕込んでいます。

dein.toml
function! s:clean_session(target_dir)
  let s:file_name = s:session_file_name(a:target_dir)
  if filewritable(s:file_name)
    call delete(s:file_name)
  endif
endfunction
function! s:clean_session_action(context)
  let s:target = a:context['targets'][0]
  let s:path = s:target['action__path']
  call s:clean_session(s:path)
endfunction
call denite#custom#action('directory', 'clean_session', function('s:clean_session_action'), {'is_quit': 0})

function! s:clean_sessions()
  call system('rm ' . s:session_file_name('*'))
endfunction
function! s:clean_sessions_action(context)
  call s:clean_sessions()
endfunction
call denite#custom#action('directory', 'clean_allsession', function('s:clean_sessions_action'), {'is_quit': 0})

vimscript 扱い慣れてはいない&自分用なのでしっかりしたデバッグはしていないですが、とりあえず期待通り動いています。

自分は作業の区切りですぐにまっさらな状態に掃除しちゃいたい派なので Neovim 起動時に Session 復元などはしておらず、起動ディレクトリごとに保存先を分けたり命名管理などもせずに気軽に上書きしています。

この辺は好みがあると思うので良きに調整してください。

以上

今回は凝集度アップとスリム化が主目的だったのであまり新しく入れたものは無いのですが、やってよかった感は強かったので良かったことをメモしてみました。

もし参考になれば嬉しいです。また、おすすめの設定などあればぜひ紹介してください。