Hori Blog

フリーランスでバックエンドエンジニアとして活動している Ryota Hori のブログです。ドメイン駆動設計や Go の話など。

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復元などはしておらず、起動ディレクトリごとに保存先を分けたり命名管理などもせずに気軽に上書きしています。

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

以上

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

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