最近のタグジャンプ事情を調べた

テキストエディタのタグジャンプ機能、最近はどんな感じなのかなあと思って調べていました。 結論としては10年前とあまり変わっていないみたい。というか新しいツールが出てきていなくて、進化が止まっている印象。

ctags

exuberant-ctagsは2009年のリリース以降開発が止まっていて、代わりにuniversal-ctagsというもののメンテナンスが続いていました。 exuberant-ctagsはMacではパッチ当てないとビルドできなかったし、もうかなり厳しそう。でもまだ使っている人多そう。

GNU Global

GNU Globalはそんなにアクティブじゃないけどメンテナンスはされていて、新しいバージョンもぽつぽつと出てました。 Macで最新リリースをビルドして見たらconfigureが通らなかったんだけど、リポジトリの方を見たらそのためのパッチは取り込まれていました。 まだ使えそうではある。

その他

さすがにもっと改良されたやつとか出てきてるだろうと思って調べたけど全然ない。タグファイル作ってタグジャンプする時代は終わったんですね。 最近だとLSPみたいなIDE機能を使うかag/ripgrepとかを使いこなすというのが主流っぽい印象を受けました。

コード書く上ではLSPやIDEが強いけど、コードを読むだけだったらタグジャンプの方が手軽で便利に感じる時もあるので、もうちょっとあってもいい気がするんですけどね。なんかいいのあったら教えてください!

Emacs + Metals + lsp-mode でScalaを書く

最近重たい処理走らせてる時とかにIntelliJが遅くてつらかったのでEmacsでもある程度コード読んだり書けたり出来るようにしました。 最近はLSPが人気あるようでEmacsでもlsp-modeというのが結構使われているみたい。Scalaの場合はLSPとしてMetalsを使うことになります。

設定

  • lsp-modeが入っていればとりあえずOK
  • flycheckを入れてコンパイルエラーの表示が出来るようにしましょう
  • lsp-uiを入れてなんかUIからポチポチ出来るようにしましょう
  • companyをコード補完機能に使いましょう
(use-package flycheck
  :init (global-flycheck-mode))

(use-package lsp-mode
  :hook
  (lsp-mode . lsp-lens-mode)
  (scala-mode . lsp)
  :config
  (setq lsp-prefer-flymake nil))

(use-package lsp-ui)

(use-package company
  :hook
  (scala-mode . company-mode)
  :config
  (setq lsp-completion-provider :capf)
  (setq company-minimum-prefix-length 1))

そんなにガリガリ設定書かなくても割といい感じになるのがすごい。

使い方

  • インストールはMetalsのドキュメントでどうぞ。
  • M-., M-, で定義元にジャンプしたり戻ったりできる。
  • キーバインドの一覧は https://emacs-lsp.github.io/lsp-mode/page/keybindings/
  • 一覧に乗ってないけど s-l g e でエラー一覧表示ができた。ドキュメント更新のpull req送った方良いのかな?

f:id:tototoshi:20210406224447j:plain
コード補完

f:id:tototoshi:20210406224357p:plain
エラーの一覧表示

f:id:tototoshi:20210406224517p:plain
コンパイルエラーの表示と選択できるアクション一覧

トラブルシューティング

デバッグはとりあえず *lsp-log* バッファを見てみると何か分かる。Metalsの場合は .metals/metals.log でも良し。

必殺再起動は M-x lsp-restart-workspace

ハマったとこ

補完がうまく行かないなあ、Any型と判定されるなあとか思ってたんだけど、monorepo的なリポジトリでworkspaceのルートが誤判定されてちゃんとプロジェクトが読み込まれていないのが原因でした。M-x lsp-workspace-folder-add で設定したら補完ができるようになりました。

あと大きなプロジェクトで遅くなるけどファイルをウォッチするか?遅くなるけど?と聞かれた時に読み込まないようにしたら補完が効きませんでした。ウォッチしないとダメなのかな。とりあえずウォッチすることにして、遅くなってから考えることにしました。

制限

今のところ機能的にここが限界だろうなあというのが2つ。

  • 高度なリファクタリング機能
    • さすがにIntelliJには勝てない。なんかすごいことやろうと思ったらIntelliJを起動しよう。
  • Javaのコードを追えない
    • Metals = scalameta + LSP なのでそりゃそうかという感じ。

感想

コード読んだり、調査だったり、ちょっとした修正には十分な機能なので嬉しい。まあガッツリコード書くときはIntelliJだけどそれ以外を軽い環境でカバー出来るのは良いですね。

EmacsをTerminalとして使う

emacs-libvterm っていうのを見つけたのだけどこれがすごく良いので紹介します。

emacs-libvterm

https://github.com/akermu/emacs-libvterm

EmacsにはTerminal系の機能としてterm-modeやeshellなどがあるんですが、遅かったり、クセが強かったりで使えてませんでした。一時期それでも使おうとしてたんですがちょっと無理でした。

それがemacs-libvtermはTerminal.appと遜色ないくらいに速く動いてくれます。しかもtmuxもちゃんと動く。 ちなみにneovim/libvterm というやつを元にしてるみたいなのでvim様には頭が上がらないですね。

f:id:tototoshi:20210401211017p:plain

試し始めはたまになんかもっさりしたり、tigみたいなncurses系のアプリで表示が崩れたりしたんだけどそれは使っている日本語フォントに起因する問題だったようで、 Cicaというフォントにしたら見事に解消しました。

https://github.com/miiton/Cica

Emacs in Emacsしてしまうのを封じる

さて、Emacsの中でシェル使えるようにしてもEmacsの中のシェルでさらにEmacsを開くとわけがわからなくなるのでそれを防止しましょう。emacs の代わりに emacsclient を使って、新たにEmacsを起動するのではなく今起動しているEmacsに新しいバッファを開くようにします。

Emacs側ではサーバーを起動して置いて

;; .emacs.d/init.el
(server-start)

さらに次のスクリプトを ~/bin/emacs として置いておきます。

#!/bin/bash

if [[ "$1" == "" ]]
then
    /path/to/emacsclient -n .
else
    /path/to/emacsclient -n "$@"
fi

逆にEmacsからterminal側に移るにも適宜keybindを設定してterminalとEmacsの行き来を便利にすると良いでしょう。

まとめ

emacs-libvtermを使うことでTerminalの中でEmacsを使うのではなくEmacsの中でTerminalが使えます。このブログはエイプリルフールではなくて真面目に書いたつもりです。

f:id:tototoshi:20210401211035p:plain

TypeScriptでgRPCしたいがいろいろあってよくわからない

用語

  • grpc/grpc-node: Node.js向けの公式プロジェクト
  • grpc-tools: grpc/grpc-node が提供しているパッケージ
  • grpc_tools_node_protoc: grpc-toolsに含まれるコマンド
  • agreatfool/grpc_tools_node_protoc_ts: grpc_tools_node_protocで吐いたコードに .d.ts を付けるサードパーティのコマンド
  • improbable-eng/ts-protoc-gen: TypeScript向けのサードパーティprotocプラグイン

Node or ブラウザ

gRPCのサーバー側はどのツールもNodeを想定しているので特に問題ない。 gRPCのクライアント側はNodeで使うことを想定しているのものとブラウザで動かすことを想定しているものがあるので注意が必要

  • ブラウザ上で動作することとを想定しているもの
    • grpc/grpc-web
  • Node用のもの
    • grpc/grpc-node (grpc-toolsのgrpc_tools_node_protoc)
  • 両方に対応しているもの
    • improbable-eng/grpc-web
      • Transportを切り替えることで両方で使えるっぽい

JavaScript での gRPC

次の2パターンがある。

  • .protoをそのまま読み込む
  • .protoからコード生成したコードを読み込む

TypeScriptから使う場合は型定義を付けたいので後者のコード生成するやり方を選ぶのが良さそうだよね。

grpc or @grpc/grpc-js

  • grpcは開発終了間近
  • 今後は @grpc/grpc-js を使うのが良さそう
  • 両方ほとんど同じだけど @grpc/grpc-js を使う場合、protobufを生成する時に --grpc_out=grpc_js:... みたいなオプションが各ツールによって提供されているので適宜ドキュメントをチェックすべし

TypeScriptサポート

できるだけ公式か公式に近いものが良いなら以下の2つが良さそう。2つあるけど動作環境を考慮すると実質一択なのかな。

  • grpc/grpc-webのTypeScriptサポート(experimental)
  • agreatfool/grpc_tools_node_protoc_tsで公式のgrpc/grpc-node (grpc-tools) に対して .d.ts を付ける

まとめ

  • grpc-webやりたいならgrpc/grpc-web
  • そうでなければgrpc-tools+grpc_tools_node_protoc_tsが一番無難そう
  • 両対応したいならimprobable-eng/ts-protoc-genも選択肢に上がってきそう

おまけ

個人用のscala-stewardを動かす

scala-stewardを使い始めました。

ちょっと使うだけならscala-stewardのrepos.mdにプルリクエスト送るだけで良くて便利なんですが、個人の雑多なプロジェクト全てにそれをやるのはちょっと気が引けるので自前でも動かせるようにしました。

今だとGitHub Actionsを使うのが手取り早いんだけど、使うトークンの権限の問題でちょっとややこしかったです。そのあたりは吉田さんのブログが詳しいです。

xuwei-k.hatenablog.com xuwei-k.hatenablog.com

自分は次の形にしました。

github.com

で、試行錯誤の上なんとかscalaおじさんがプルリクくれるようになりました。

f:id:tototoshi:20210213145753p:plain Update sbt to 1.4.7 by scala-ojisan · Pull Request #3 · tototoshi/hello.g8 · GitHub

sbtn 便利かも

sbt 1.4.0 から sbtn という機能が追加されました。

https://eed3si9n.com/sbt-1.4.0-beta

Native thin client sbt 1.4.0 adds an official native thin client called sbtn that supports all tasks. > https://github.com/sbt/sbtn-dist/releases/tag/v1.4.0-RC2

This lets you run sbt tasks from the system shell as:

$ sbtn compile

$ sbtn shutdown

The native thin client will run sbt (server) as a daemon, which avoids the JVM > spinup and loading time for the second call onwards. This could an option if you > would like to use sbt from the system shell such as Zsh and Fish.

Remember to call sbtn shutdown when you're done!

Later on, sbt script will also support --client option to run the native thin client:

$ sbt --client compile

$ sbt --client shutdown

#5620 by @eatkins

sbtn 一度起動すると sbt をデーモンとして常駐させ、以降は sbt を起動するのではなくすでに起動している sbt に接続しに行くため起動時間が短縮されます。

emacs 使ってる人には emacsclient ですって言えば通じそう。あと sbt と同じ Scala のビルドツールである mill も似たようなことやってますよね。mill 最近使い始めたばかりでよく知らないけど。

sbt は起動が遅いので対話シェルを起動させておいてそれを利用するのが普通でしたが、sbtn によってシェルを起動しっぱなしにする必要がなくなります。私は気づくと tmux のウィンドウが無限に開いていて、sbt 起動してるのはどこのウィンドウだっけ??と探して回るのを毎日やっているのですが、sbtn を使うと今いる window でコマンド打てばいいのでそれがなくなりそうです。

あと他のシェルスクリプトとの連携がしやすくなるのも良いですね。今までは

#!/bin/sh
sbt compile
set_up_database
sbt test
tear_down_database

みたいなスクリプトを書くのは sbt の起動が遅いので辛く、シェルスクリプト側の処理を sbt の方に組み込むことになり、sbt 職人が build.sbt に

lazy val setUpDatabase = taskKey[Unit]("setup_database...")
lazy val tearDownDatabase = taskKey[Unit]("teardown_database...")
...

とか書いてドヤッって感じだったんですが、sbtn を使えば

#!/bin/sh
sbtn compile
set_up_database
sbtn test
tear_down_database

でもう十分ですね。build.sbtのダイエットができそうです。

追記

  • project/build.properties のバージョンを上げるのに加えてsbtコマンドのバージョンも 1.4 に上げましょう。
  • sbtnでPlayのプロジェクトをrunするとプロジェクトは起動するけれどsbtn側で標準入力を受け取れないっぽい。(Playプロジェクトのログを受け取る前にデタッチされてそう?)

Modules should be fast and side-effect free と Guice は言っているがPlay Frameworkはそうなってはいない話

github.com

Modules should be fast and side-effect free

Modules should be fast and side-effect free(モジュールは速くて副作用なしであるべき)と GuiceWiki にはあります。

これは Guice モジュールの中で DB に接続したりスレッド開始したりするなという話で、これに従うのならば Guice の Module (extends AbstractModule したクラスの中とか)の他にも Module を構成する各クラスのコンストラクタの中でも副作用を起こしてはいけないことになります。

副作用を起こしてはいけない理由としては

  • Modules start up, but they don't shut down.
  • Modules should be tested.
  • Modules can be overridden.

Guice は挙げています。

Guice の意図に沿ったコードを書くと初期化処理、終了処理のためのメソッドをコンストラクタ以外に用意して、アプリケーションの起動、終了時になんらかの形で呼んであげることになります。

public interface Service {
  void start() throws Exception;
  void stop();
}
 public static void main(String[] args) throws Exception {
    Injector injector = Guice.createInjector(
        new WebserverModule(),
        ...
    );

    Service webserver = injector.getInstance(
        Key.get(Service.class, WebserverService.class));
    webserver.start();
    addShutdownHook(webserver);
  }

サンプルでは単純ですが徹底するとなると例えば設定ファイルを読み込むとかも禁止となるのでなかなか厳しい制限にも感じます。(実際には設定ファイル読み込みとかは OK にして、スレッド開始とかは NG にする、くらいが落とし所かも?)

Play Framework の場合

Play Framework は Guice を使っていますが、Guice のこの主張を完全に無視して副作用をバリバリにつかっています。Play を構成するクラスは大体コンストラクタでスレッド開始したり設定読み込んだりとやって、終了処理はコンストラクタ内で ApplicationLifecycle に登録します。

@Singleton
class NanrakanoService @Inject()(lifecycle: ApplicationLifecycle) {
  val resource = new NanrakanoBackgroundResource().start()
  lifecycle.addStopHook(() => Future.successful(resource.shutdown()))
}

Guicewiki を見たあとでこれを見るとギョッとするんですが実際のところそんなに困ることはないです。

とはいえやっぱり時々は困るし、Guice の推奨しない方法で Guice をヘビーに使っているというのは精神衛生上あまり良くはありません。そして実はコンストラクタで副作用を起こすようになったのは Play が Guice を使い始めた Play 2.4 からの話で、Guice を使っていなかった Play 2.3 以前の Plugin の仕組みでは副作用のためのメソッドが分かれていたのです。

package plugins

import play.api.{Plugin, Application}

class MyPlugin(app: Application) extends Plugin {
  val myComponent = new MyComponent()

  override def onStart() = {
    myComponent.start()
  }

  override def onStop() = {
    myComponent.stop()
  }

  override def enabled = true
}

Guice を使う前は副作用が分かれていたのに、互換性を壊してまで Guice を使い始めたら副作用起こすようになったというのがなんかチグハグだなあって思います。