2018 年振り返り


今年もほぼお仕事のコミット。
py-pixela を作ったくらい。あとは Vim script。

健康問題

年末(先週くらい)から風邪をひいている。体が弱いくせに風邪は滅多にひかないと言う特殊体質なので、咳が止まらなくて寒気でからが震え、頭痛がする。
加齢とともに回復力が落ちてきた気はする。


2017 年は肩とか耳とかが酷かったけど、今年はマシになった。
肩は上がるようになったが、痛みがないわけではないので、うまく付き合っていくしかなさそう。

体重

去年の年末に少し減らしたけど、正月明けたら見事にリバウンドして上昇。
夏に健康診断時に体重の数値がひどいことになっていたので、痩せる決意をする。
4 ヶ月で大体 8kg 減。来年もこのままこのペースを続けていきたい。

買ってよかったもの

割と機材にお金を突っ込んだ気がする。

  • Shure BETA 58A
  • マイクスタンド
  • Mackie Onyx Black Jack
    • もう絶版で最後に残ってたのがあったからそれ
    • 仕事用のデスクは奥行きが狭く、小さいものがよかった


ビデオ会議で声が聞きやすくなったと言われた(自分ではわからない)。
58A はダイナミックマイクなので、口元につけるオンマイク状態じゃないと厳しいことがわかった(油断すると聞こえないと言われる)。
同僚が導入した M-Audio uber はとても聞き取りやすかったので、次はコンデンサーマイク(BETA 87Aあたり)かなぁと思っている。
コンデンサーマイクになると保管問題が出てくるし、そうなると防湿庫も欲しいと思ってる(カメラも入れたいし)。

来年

マイペースにいきたい。
健康はなんとかせんといかんなぁ。

tsuquyomi を魔改造している

https://qiita.com/advent-calendar/2018/vim 22 日目の記事です。

tl;dr

tsuquyomi でエラーチェックが同期で走って Vim の UI をブロックしストレスがたまるから、魔改造して非同期で動く仕組みを作った。
https://github.com/heavenshell/tsuquyomi/tree/feature/async

はじまり

今年はお仕事で TypeScript を本格的に書くようになった。
2016 年に TypeScript を少し書いた時も tsuquyomi を使っていた。
今回も tsuquyomi を入れて、自作の Tslint チェックをする Vim plugin を作って開発をしている。
保存時に tsuquyomi が TSServer にチェックを行っていた。
(Tslint は完全に非同期になっているので、InsertLeave 時にチェック)


最初はプロジェクトのソースコードのファイル数や依存ライブラリ数が少ないので特に速度に問題を感じなかったが、ある程度の規模になってきてから保存しチェックが終わるまで、体感的に数秒かかり Vim の UI をブロックする場合があり、ストレスになってきた。
特に起動直後の :TsuGeterr が遅い。

シンタックスエラーがあるファイルを開き :TsuGeterr をすると、Vim の UI がブロックされていることがわかると思う。

なおこの問題は tsuquyomi の Issue にもある。
https://github.com/Quramy/tsuquyomi/issues/241

ale.vim

そんな時に同僚に ale.vim がエラーチェック、補完とともによくできていることを教えてもらった。
ale.vim のエラーチェックが非同期で動作するのは勿論知っていたが、lsp による補完までサポートしているのは知らなかった。
少し使ってみたが、現状ストレスに感じているエラーチェックも完全にバックグランドで動作し、Vim の UI をブロックすることないのが素晴らしかった。
一方で非同期で補完しているため、補完のポップアップがちらつくのが少し気になった。


目に付いた欠点はその程度(あとは TypeScript + Prettier + tslint --fix との連携がうまくいかなかったが、おそらくこれは自分の設定が悪そう)だが、ale.vim は非常によくできていて、なぜか悔しかった。


また vim-lsp は試していない(mattn さんがごくごく最近メンテナの一人になられたので、今後は安心して使えるとは思う)が、おそらく非同期でいい感じで動くのだろう。
その他の最近の TypeScript な Vim プラグインも試していないが、おそらく似たり寄ったりだろう。

tsuquyomi はなぜ UI をブロックするのか

tsuquyomi も TSServer を使っているし、Vim8 の Job / Channel を使っているので、ale.vim と同様のことができるはずと仮説を立てて、tsuquyomi のコードを読んだ。


Vim8 だと TSServer には `ch_sendraw()` でリクエストしている。
https://github.com/Quramy/tsuquyomi/blob/master/autoload/tsuquyomi/tsClient.vim#L154


問題はレスポンスの箇所で、指定してたレスポンス長を超えるまでループで待っている。
https://github.com/Quramy/tsuquyomi/blob/master/autoload/tsuquyomi/tsClient.vim#L160
https://github.com/Quramy/tsuquyomi/blob/master/autoload/tsuquyomi/tsClient.vim#L178

結果的に TSServer がレスポンスを返して QuickFix に追加するまでが同期として動いており、UI をブロックしている。
特に起動直後は TSServer からのレスポンスが遅い(これは ale.vim でも同様で、TSServer 上の仕様であると思う)。
そのため上記のスクリーンキャストのように :TsuGeterr を実行すると、遅い場合は数秒固まったようになる。


tsuquyomi の関数のヘッダーコメントに `pseudo synchronously` と書いてあるので意図した動きだろうが、エラーがあれば QuickFix に入れて、ユーザーに通知するだけで良いので、レスポンスをわざわざ待つ必要はない。
ちなみに tsuquyomi が作られた頃には Vim8 の Job/Channel なんてなかったのだから、そういう実装になっているのは理解できる。

魔改造の方針

元からある TsuGeterr を改良するのではなく、TsuAsyncGeterr というように新しくインターフェイスを加える方針にする。
現状はエラーチェック(TsuGeterr)だけだが、他の機能も容易に非同期化できる作りにしたい。
なお本魔改造は Vim8 の Job/Channel に全力で依存しているので、NeoVim は一切考慮していない。
(元々 tsuquyomi も NeoVim は考慮していない)

魔改造した箇所

まずは Vim8 の Job を開始しているところにコールバックを仕込む。


https://github.com/heavenshell/tsuquyomi/blob/feature/async/autoload/tsuquyomi/tsClient.vim#L87
これで TSServer が STDOUT に出力があるたびにコールバックが反応する。
今回はエラーチェックに関するものだけを受け取りたい。


コールバックを受け取り応答メッセージを解析しエラーがあれば QuickFix に入れ、ユーザーに通知するコールバックを呼び出すという設計にした。


https://github.com/heavenshell/tsuquyomi/blob/feature/async/autoload/tsuquyomi/config.vim#L313
tsuquyomi が起動した際にここで、エラーチェックに関する関数を登録する。
TSServer からの応答メッセージにエラーがあればそれを抽出し、requestCompleted を受けたら、QuickFix に設定するリストを作成し、通知用のコールバック関数に送信するようにした。


これにより Vim 起動直後にエラーチェックを行っても UI をブロックすることがなくなった。


上記は :TsuGeterr 時と同様の条件で行った様子。:TsuAsyncGeterr 実行時にすぐに Vim の UI が操作できるのがわかると思う。


また TextChange, InsertLeave, BufWritePost イベントを登録することによって、シームレスにエラーを通知できるようになった。


ただし :TsuAsyncGeterr は :TsuGeterr に挙動を合わせているため、エラーがあれば、QuickFix を自動で開き、エラーがなければ、自動で QuickFix を閉じる。
そのため TextChange や InsertLeave に紐付けていると、QuickFix を自動で開くのが UI の操作をブロックして鬱陶しい。
エラーの通知は ale.vim 同様そっとユーザーに知らせ、ユーザーが自分で QuickFix を開けば Vim の操作をブロックすることがなくなる。


下記のように自分独自の通知先関数を .vimrc で登録すると、QuickFix の制御をできるようにした。

let s:ts_notify = 0
function! s:ts_callback(qflist)
  if s:ts_notify == 1
    call setqflist(a:qflist)
    let cnt = len(getqflist())
    if cnt > 0
      echomsg printf('[TypeScript] %s errors', cnt)
    else
      echomsg '[TypeScript] No error'
    endif
  endif
  let s:ts_notify = 0
endfunction

function! s:ts_quickfix()
  let s:ts_notify = 1
  if g:tsuquyomi_is_available == 1
    call tsuquyomi#registerNotify(function('s:ts_callback'), 'diagnostics')
    call tsuquyomi#asyncCreateFixlist()
  endif
endfunction
autocmd InsertLeave,TextChanged,BufWritePost *.ts,*.tsx silent! call s:ts_quickfix()

tsuquyomi#registerNotify() は登録したコールバック関数の引数に QuickFix に入れるリストを詰めて呼び出すので、それを受けて、自分で setqflist() を呼び、エラーがあればエコーメッセージを出し、ユーザーに通知をする。

上記は、間違った型を設定しようとして、tsuquyomi がエラーを通知を受けた様子。
(エラーハイライトは別のプラグインを使っている)

拡張性

今はエラーチェックのみだが、例えば今後補完を非同期で実装したいとなると、エラーチェックと同様に専用の関数を作成し、 https://github.com/heavenshell/tsuquyomi/blob/feature/async/autoload/tsuquyomi/config.vim#L293 ここで登録し、応答メッセージをパースし、補完候補を構築し、Vim の complete() を呼べば良い。

おまけ

tsuquyomi で関数などのシグネチャがどう定義されているかを知りたい場合などが多々ある。
ale では :ALEHover を利用すれば良い。
同様のことを tsuquyomi でするには以下のように .vimrc に登録すればできる。

function! s:ts_hint()
  let content = tsuquyomi#hint()
  silent pedit __TsuquyomiScratch__
  silent wincmd P
  setlocal modifiable noreadonly
  setlocal nobuflisted buftype=nofile bufhidden=wipe ft=typescript
  put =content
  0d_
  setlocal nomodifiable readonly
  silent wincmd p
endfunction

command! TsHint silent! :call s:ts_hint()
noremap <silent> <buffer> <Plug>(TsuHint) :TsuHint<CR>
autocmd FileType typescript nnoremap <buffer> <leader>h :TsHint<CR>

まとめ & お願い

Vim7 時代の少し前に作られたプラグインを非同期化を試みた。
色々お世話になったプラグインなので、改良して貢献をしたい。


もし tsquyomi を使っていらっしゃる方がおり、エラーチェックで UI がブロックされて辛いという方は是非 https://github.com/heavenshell/tsuquyomi/tree/feature/async を試して頂いて、フィードバックを頂きたい。
自分で仕事でも使っていて、今の所それほど怪しい動きはしていないのでそのまま PR しようかと思ったが、先にご意見などを受けてから PR したい(集まらなければそのまま PR 出そうと思う)。
Twitter で @heavenshell あたりにメンションいただければとてもありがたいです。

謝辞

Vim アドベントカレンダーに登録したものの、今回のネタどうしようと悩んでた時に TypeScript で ale.vim がいけているというのを教えてくれた同僚に感謝します。

Jest の create() がエラーになるお話

忘備録。
Jest と react-test-renderer の create() を使って snapshot の比較のテストが落ちるようになった。

TypeError: Cannot read property 'addEventListener' of null 

さんざか調べまくった結果 react-test-renderer では ref を使ってコールバックを仕込んでるとサポート外で落ちる模様。
https://github.com/facebook/jest/issues/5462


createNodeMock を使うか Enzyme 使えと Issue に有った。
ライブラリの深いところで ref を使ってたので、createNodeMock は厳しそうだったので、やむをえなく Enzyme 使った。


react-router-redux とか使ってるので、単純に mount() したものを enzyme-to-json で toJson() しただけでは snapshot が意図したものと違うものが出来上がるので、toJson() の第二引数の { mode: 'deep' } をするようにした。

react-router-redux でハマった

忘備録。


react-router-redux v5.0.0-alpha.9 で表示している URL と match.params が一致していないことが起きた。
症状的には https://s8a.jp/react-router-redux-5-does-not-work-correctly#%E7%8F%BE%E8%B1%A1 こちらと同じ。
ブラウザの戻る/進むボタンだが、発生したのは history.push() で URL を書き換えた際に URL と match.params に入ってる値が違う。


どうも仕様みたい。
https://github.com/ReactTraining/react-router/pull/4668#issuecomment-285260711


example にも同様のコードがある。
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-redux/examples/BasicExample.js
おそらく Store にある location が同期が取れてないので、ルーティングが始まる前に connect で Store にある location の情報を最新の location で書き換える。

docker-compose で mysql を立ててローカルで開発する

忘備録。


古い Django で作られたシステムがあって、訳あってローカルで動かしたい。
docker-compose があったので、動かした際にハマった問題。

version: '3.3'

services:
  db:
    image: 'mysql:5.7.22'
    restart: always
    ports:
    - '3306:3306'
    environment:
      MYSQL_DATABASE: root
      MYSQL_USER: foo
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'

docker-compose up してビルドして MySQL を起動する
次にローカルから MySQL につなぎ適当にユーザーと権限を付与。
めんどかったので全て解放した。

$ mysql -u root -h 127.0.0.1            
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 96
Server version: 5.7.22 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>


Django の設定ファイルの DB のホストを `127.0.0.1` にする。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'NAME': 'DBNAME',
        'USER': 'USERNAME',
        'PASSWORD': '',
    },
}

あとは syncdb して migrate して runserver したらいけた。


ハマったポイントが、 HOST 名。最初 Docker 内のホスト名にしたが、127.0.0.1 で良いみたい。

illegal hardware instruction

freezegun を使ってテストをした際に使っているテストで illegal hardware instruction が出た。


https://github.com/spulec/freezegun/issues/222
環境的には Virtualenv で Python3.6.2(ちょっと古い)で発生。
CI とか他の環境ではでない。


ググったり、上記の Issue を見たけどよく話からなった。
Python3 のバージョンが少し古かったので、brew の環境を入れ直して 新しいバージョンで Virtualenv の環境を作り直して、実行したら直った。


不思議。


ついでに homebrew の Python が例のアレによって python@2 を明示的にリンクしないと `/usr/local/bin/` に python として存在しなくなったので、Vim を起動すると Python のリンクができずに死亡した。
MacPorts だと port select で Python3 を python としてできるけど、homebrew ではどうやってやるんだろう。
早くローカルからは Python2 はなくなって欲しい…。