GitHub Releasesから実行可能ファイルをダウンロードするCLIツールを作った

長らくZshプラグインマネージャとして使用してきたZinitからsheldonへと移行するため、GitHub Releasesから実行可能ファイルをダウンロード・インストールするCLIツール gh-rd を Deno で作成しました。

github.com

  • これは何?
  • 経緯
    • Zinitをやめた理由
    • 代替手段の検討
      • Linuxbrew
      • aqua
  • まとめ
続きを読む

自作シェルプロンプトを (8ヶ月前に) 作り直した - croque

数度本ブログ内で記事にしていますが、自作したシェルプロンプト almel を長らく使用していました。

ryooooooga.hateblo.jp

ryooooooga.hateblo.jp

数点思うところがあり、シェルプロンプトを (8か月前に) 新たに作り直しました。 作成したプロンプトは croque (読みは "くろっく" ではなく "くろっけ") という名前で公開しています。

github.com

  • almelとの違い
    • RPROMPT
    • GitHub の Pull Request, GitLab の Merge Request セグメントの追加
    • コンフィグファイルの見直し
  • まとめ
続きを読む

Lazygitに投げたPRまとめ

この2年ほど、LazygitにちまちまとPull Requestを投げています。 マージされたものもなんだかんだでほどほどの数になったので、それぞれについて振り返ってみます。

ちなみに私は英語が大の苦手であるため、PRに書かれている英語はGoogle翻訳とパッションによって構成されています。

  • Lazygitとは
  • 1. fix a bug related to inserting and deleting wide characters by Ryooooooga · Pull Request #10 · jesseduffield/gocui · GitHub
  • 2. Fix command escaping #1168 by Ryooooooga · Pull Request #1173 · jesseduffield/lazygit · GitHub
  • 3. Improve command execution with strange file names. by Ryooooooga · Pull Request #1177 · jesseduffield/lazygit · GitHub
  • 4. Fix a placeholder for remote name edit box by Ryooooooga · Pull Request #1178 · jesseduffield/lazygit · GitHub
  • 5. the patch panel would crash if the filename contained an odd number of double quotes by Ryooooooga · Pull Request #1434 · jesseduffield/lazygit · GitHub
  • 6. Fix submodule command escaping #1436 by Ryooooooga · Pull Request #1437 · jesseduffield/lazygit · GitHub
  • 7. Fix stash submodule #1436 by Ryooooooga · Pull Request #1440 · jesseduffield/lazygit · GitHub
  • 8. Fix crash on remove tracked files #1480 by Ryooooooga · Pull Request #1481 · jesseduffield/lazygit · GitHub
  • 9. Improved command execution by Ryooooooga · Pull Request #1501 · jesseduffield/lazygit · GitHub
  • 10. Fix staged renamed file with unstaged in file pane #1408 by Ryooooooga · Pull Request #1438 · jesseduffield/lazygit · GitHub
  • 11. Fix panic during merge conflict #1375 by Ryooooooga · Pull Request #1446 · jesseduffield/lazygit · GitHub
  • 12. Make os.editCommand customizable using template by Ryooooooga · Pull Request #1413 · jesseduffield/lazygit · GitHub
  • 13. Fix open command in Windows #1403 by Ryooooooga · Pull Request #1448 · jesseduffield/lazygit · GitHub
  • 14. Change not to use cat command #1299 by Ryooooooga · Pull Request #1449 · jesseduffield/lazygit · GitHub
  • 15. Fix deletion of unmerged branches in languages other than English by Ryooooooga · Pull Request #1463 · jesseduffield/lazygit · GitHub
  • 16. Save patch files in TempDir #1318 by Ryooooooga · Pull Request #1494 · jesseduffield/lazygit · GitHub
  • 17. Fix open link command in Windows #470 by Ryooooooga · Pull Request #1504 · jesseduffield/lazygit · GitHub
  • 18. CI tests on Windows by Ryooooooga · Pull Request #1503 · jesseduffield/lazygit · GitHub
  • 19. Fix rendering of view titles containing multi-byte char by Ryooooooga · Pull Request #15 · jesseduffield/gocui · GitHub
  • 20. Improve backward compatibility by Ryooooooga · Pull Request #1529 · jesseduffield/lazygit · GitHub
  • 21. Fix multibyte initial characters by Ryooooooga · Pull Request #1540 · jesseduffield/lazygit · GitHub
  • 22. Support `merge.conflictStyle diff3` #940 by Ryooooooga · Pull Request #1447 · jesseduffield/lazygit · GitHub
  • 23. Fix permissions issue with temp dir #1318 by Ryooooooga · Pull Request #1852 · jesseduffield/lazygit · GitHub
  • 24. Fix an error when pressing enter on the first commit in the commits panel by Ryooooooga · Pull Request #1483 · jesseduffield/lazygit · GitHub
  • 25. Fix diff of renamed files by Ryooooooga · Pull Request #1859 · jesseduffield/lazygit · GitHub
  • 26. Support open/edit files in merge conflict panel by Ryooooooga · Pull Request #1857 · jesseduffield/lazygit · GitHub
  • 27. Fix line wrapping when combining wide characters with eraseInLineFromCursor. by Ryooooooga · Pull Request #18 · jesseduffield/gocui · GitHub
  • 28. fix not to trim renamed file names by Ryooooooga · Pull Request #1876 · jesseduffield/lazygit · GitHub
  • 29. Improve default editCommandTemplate by Ryooooooga · Pull Request #1891 · jesseduffield/lazygit · GitHub
  • 30. Display Nerdfont icons by Ryooooooga · Pull Request #1894 · jesseduffield/lazygit · GitHub
  • 31. Fix a crash that occurred when pressing enter on an empty commit, reflog, or stash panel by Ryooooooga · Pull Request #1914 · jesseduffield/lazygit · GitHub
  • 32. Add Japanese translation by Ryooooooga · Pull Request #1913 · jesseduffield/lazygit · GitHub
  • 33. add ability to edit hunk by Ryooooooga · Pull Request #1912 · jesseduffield/lazygit · GitHub
  • 34. Fix the prompt height and appStatus width calculation when using multi byte characters by Ryooooooga · Pull Request #1920 · jesseduffield/lazygit · GitHub
  • 35. fix subtitle collision by Ryooooooga · Pull Request #20 · jesseduffield/gocui · GitHub
  • 36. Fix copying of the commit author to the clipboard by Ryooooooga · Pull Request #1923 · jesseduffield/lazygit · GitHub
  • 37. Accept named colors for authorColors #1879 by Ryooooooga · Pull Request #1910 · jesseduffield/lazygit · GitHub
  • 38. Improve loading speed of commits and reflog when `log.showSignature=true` by Ryooooooga · Pull Request #1911 · jesseduffield/lazygit · GitHub
  • 39. WIP: Fix ambiguous refname by Ryooooooga · Pull Request #1944 · jesseduffield/lazygit · GitHub
  • 40. Fix linter errors by Ryooooooga · Pull Request #1945 · jesseduffield/lazygit · GitHub
  • 41. add ability to customize time format in commit log by Ryooooooga · Pull Request #1948 · jesseduffield/lazygit · GitHub
  • 42. Localize panel titles by Ryooooooga · Pull Request #1936 · jesseduffield/lazygit · GitHub
  • 43. add missing `keybinding.branches.renameBranch` to Config.md by Ryooooooga · Pull Request #2002 · jesseduffield/lazygit · GitHub
  • 44. docs(Config.md): fix docs on specifying config file by Ryooooooga · Pull Request #2022 · jesseduffield/lazygit · GitHub
  • 45. Change not to run `Generate Sponsors` action on the fork repository by Ryooooooga · Pull Request #2039 · jesseduffield/lazygit · GitHub
  • 46. fix subtitle collision by Ryooooooga · Pull Request #20 · jesseduffield/gocui · GitHub
  • 47. Add `notARepository: quit` by Ryooooooga · Pull Request #2098 · jesseduffield/lazygit · GitHub
  • 48. improve integration test portability by Ryooooooga · Pull Request #2124 · jesseduffield/lazygit · GitHub
  • 49. fix initial scroll position of edit box by Ryooooooga · Pull Request #2123 · jesseduffield/lazygit · GitHub
  • 50. fix initial origin of editor box by Ryooooooga · Pull Request #2146 · jesseduffield/lazygit · GitHub
  • 51. fix initial origin of commit message panel by Ryooooooga · Pull Request #2221 · jesseduffield/lazygit · GitHub
  • 52. Ignore stderr when loading git status by Ryooooooga · Pull Request #2081 · jesseduffield/lazygit · GitHub
  • 53. Add Ctrl+W keybind to delete word like shell by Ryooooooga · Pull Request #16 · jesseduffield/gocui · GitHub
  • 54. add support for emacs keybindings by Ryooooooga · Pull Request #2183 · jesseduffield/lazygit · GitHub
  • 55. Fix an issue where long inputs containing wide characters cause blank spaces on the right side. by Ryooooooga · Pull Request #17 · jesseduffield/gocui · GitHub
  • 56. fix: improve hard tab rendering by Ryooooooga · Pull Request #21 · jesseduffield/gocui · GitHub
  • 57. Support ANSI reset sequence by Ryooooooga · Pull Request #22 · jesseduffield/gocui · GitHub
  • 58. Navigate by word using alt+left/right. by Ryooooooga · Pull Request #19 · jesseduffield/gocui · GitHub
  • 59. navigate by word in the editor by Ryooooooga · Pull Request #2204 · jesseduffield/lazygit · GitHub
  • 60. Allow `OSCommand.Quote` to be invoked within a custom command by Ryooooooga · Pull Request #2193 · jesseduffield/lazygit · GitHub
  • 61. fix stash with empty message by Ryooooooga · Pull Request #2218 · jesseduffield/lazygit · GitHub
  • 62. add stash icon by Ryooooooga · Pull Request #2219 · jesseduffield/lazygit · GitHub
  • 63. allow nil in keybindings by Ryooooooga · Pull Request #23 · jesseduffield/gocui · GitHub
  • 64. allow `null` in keybindings by Ryooooooga · Pull Request #2222 · jesseduffield/lazygit · GitHub
  • 65. rename stash by Ryooooooga · Pull Request #2220 · jesseduffield/lazygit · GitHub
  • 66. Ignore OSC sequence by Ryooooooga · Pull Request #24 · jesseduffield/gocui · GitHub
  • 67. bump gocui by Ryooooooga · Pull Request #2276 · jesseduffield/lazygit · GitHub
  • 68. fix ambiguous branch name by Ryooooooga · Pull Request #2224 · jesseduffield/lazygit · GitHub
  • 69. fix goroutine leak by Ryooooooga · Pull Request #25 · jesseduffield/gocui · GitHub
  • 70. fix diff scroll #2309 by Ryooooooga · Pull Request #2326 · jesseduffield/lazygit · GitHub
  • まとめ
続きを読む

自作Zshプラグイン・CLIツールの使用状況報告

ここ数年の間にいくつかの小さいZshプラグインCLIツールを作成しました。

頻繁に使用しているものもあればその実そうでないものもあります。本記事では現在の自作プラグイン・ツールの使用状況を報告します。

1. Almel

Almelは高速に動作するカスタマイズ性に優れたシェルプロンプトです。

ryooooooga.hateblo.jp

かれこれ3年以上使用しており、自作Zshプロンプトを非同期対応した - 茅の下で紹介したように非同期描画に対応したことで(自作なので当たり前ではあるのですが)不満点を克服し、より手放し難いものになりました。

ryooooooga.hateblo.jp

上記記事を書くにあたり参考のために人気の高い Powerlevel10k を使用したこともあったのですが、セットアップの容易さなどを鑑みても今後他のプロンプトに乗り換えることはおそらくありません。

今後の機能追加を積極的に行っていくつもりはない(なにせ現状に満足しているため)一方で、コンフィグファイルの構造などには反省点も多いので、別の名前で作り直すことはひょっとしたらあるかもしれません。

2. Zouch

Zouchはテンプレートから新規ファイルを生成するコマンドです。

ryooooooga.hateblo.jp

これは現在でもちょくちょく使っており、また-rオプションを付けることで中間ディレクトリの作成も行えて便利なため、touchコマンドの代わりに使用することもしばしばです。

もっともよく生成しているファイルはおそらく .editorconfig.gitignore で、これらはFZFを利用してインタラクティブに内容を生成できるようにしています。

一方で、一定のルールに従って複数のファイルを同時に出力したい (hoge.gohoge_test.go, src/main/java/.../Piyo.javasrc/test/java/.../PiyoTest.java など) という瞬間も多く、そのような場合のためにmdmgの導入を検討しています。

blog.himanoa.net

3. zabrze

zabrzeは略語展開を行うZshプラグインです。

ryooooooga.hateblo.jp

もっとも使用頻度が高く、もっとも生産性を向上させているZshプラグインです。

evaluateやzabrzeのアイデアの根幹であるcontextといった機能は上記記事公開後に(パクリ元である)zeno.zshにも逆輸入され、さらにzeno.zshの実行パフォーマンスも改善されたため記事中で紹介したいくつかのユースケースはzeno.zshで代替可能になったのですが、その後のzabrzeへの機能追加によってより発展的な展開が行えるようになりました。

追加された機能:

  1. 正規表現による略語マッチ
  2. コマンド全体の置換
  3. 条件付きの略語展開

1. 正規表現による略語マッチ

マッチする略語のパターンを正規表現で指定できるようになりました。

abbrevs:
  # cp を cp -i に, mv を mv -i  に展開する 
  - name: cp/mv
    abbr-pattern: ^(cp|mv)$
    snippet: $abbr -i # マッチした略語は $abbr という変数でアクセスできる
    evaluate: true

  # .N を awk '{ print $N }' に展開する
  - name: .N
    abbr-pattern: ^\.\d+$
    snippet: awk '{ print ${abbr/./$} }'
    evaluate: true

これにより、複数の略語を1つの設定で記述できるようになりました。

2. コマンド全体の置換

以前は入力の置換対象は略語のみに限られていましたが、コマンド全体を置換することが可能になりました。

abbrevs:
  # cd - を popd に展開する
  - name: cd -
    abbr: "-"
    snippet: popd
    action: replace-all # コマンド全体を置換する
    global: true
    context: ^cd\s+-$

また、上で紹介した正規表現による略語マッチと組み合わせることでsuffix aliasを実現できるようになりました。

abbrevs:
  # *.py を python3 *.py に展開する
  - name: python3 *.py
    abbr-pattern: \.py$
    snippet: python3 $abbr
    evaluate: true

3. 条件付きの略語展開

略語展開の条件を設定できるようになりました。

これにより「macOSでのみ展開する」、あるいは「特定のコマンドが存在している場合のみ展開する」略語が実現できるようになりました。

abbrevs:
  # macOSでのみ展開される
  - name: chrome
    abbr: chrome
    snippet: open -a 'Google Chrome'
    if: '[[ "$OSTYPE" =~ darwin ]]'

  # trashコマンドが存在するときのみ rm を trash に展開する
  - name: trash
    abbr: rm
    snippet: trash
    if: (( ${+commands[trash]} ))

  # trashコマンドが存在しないときは rm を rm -ri に展開する
  - name: rm
    abbr: rm
    snippet: rm -ri

4. qtmut

qtmut はTmuxinatorの代替ツールです。

ryooooooga.hateblo.jp

あんまり使っていません (ZLEに組み込んでいるため呼び出しはしているが、本来の用途では使っていない)。Tmuxを生で呼び出す場合に比べやや遅く感じるためRustなどを用いて再実装することを考えていますが、使用頻度は高くないので腰は重いです。

5. commitizen-deno

commitizen-deno はDeno製のcommitizenクライアントです。

ryooooooga.hateblo.jp

たまに使用します。が、直接 lazygit で feat(Hoge): .... のようなコミットメッセージを入力してしまうことの方が多いです。

6. qwy

qwy はpmyのクローンです。

github.com

ryooooooga.hateblo.jp

紹介記事を書いたことはありませんが、半年ほど前に作成したツールです。

pmyから使用していない機能を削除し、設定ファイルの項目を整理することで設定間の再利用性を高めたものです。が、多くの人にとってはzeno.zshで十分なのでREADMEからして他人に使わせる気がありません。

現在運用している設定のほぼ全てが以前pmyで使用していたものを移植したものです。

ファイル名の補完に利用することが多く、1日に1回程度のあまり高くない頻度で利用しています。

まとめ

よく使用しているものもあればそうでもないものもありますが、使っていない=存在している価値がない, 作る価値がない、ということではないですし、手札を増やすことは良いことなので今後もそんなに使わないものを作っていきたいですね。

自分の運用している設定はdotfilesリポジトリで確認できます。

ヘッダーオンリーのC言語向け単体テストフレームワークCUTEを作った

C99以降向けのヘッダーオンリーで動作する単体テストフレームワークCUTEを作成しました。

github.com

モチベーション

C言語のコードに対する単体テストを書く際、ごく簡単なものであれば assert() を使用することが多いと思います。

しかし、テスト対象が複雑であったり、テストの分量が多い場合には assert() のみでは機能不足を感じることが今まで多々ありました。

そこで、自分用に必要十分な機能を備え、なおかつシンプルな使い心地の単体テストフレームワークを作成しました。

動作環境

C89以前、およびMSVCはサポートしていません。

使用方法

CUTEはヘッダーオンリーのライブラリであるため、適当なディレクトリに cute.h をダウンロードし、includeする だけで使用できます。

また、CMakeを使用している場合はFetchContentを使用することで以下のようにプロジェクトをインポート出来ます。

include(FetchContent)

FetchContent_Declare(cute
    GIT_REPOSITORY "https://github.com/Ryooooooga/cute.git"
    GIT_TAG        "main"
)

FetchContent_MakeAvailable(cute)

target_link_library(your_project cute)

コード例

CUTEを使用した簡単なテストの記述例を以下に示します。

#include "cute.h"

int factorial(int n) {
    if (n > 0) {
        return n * factorial(n - 1);
    }
    return 1;
}

TEST(factorial) {
    EXPECT(factorial(0), eq(1));
    EXPECT(factorial(1), eq(1));
    EXPECT_MSG(factorial(5), eq(120), "5! == 120 (actual %d)", factorial(5));
}

TEST(string) {
    const char *s = "Hello, world!";

    ASSERT(s, is_not_null);
    EXPECT(s, eq_str("Hello, world!"));
    EXPECT((s, 4), eq_str_n("Hell"));
    EXPECT(s, contains("world"));
    EXPECT(s, not(contains("nya")));
}

int main(void) {
    RUN_TESTS() {
        RUN(factorial);
        RUN(string);
        return DUMP_RESULT();
    }
}

その他の例は example.c で確認できます。

基本的な書き方

上記の通り、基本的な条件は以下のような形式で記述します。

  • EXPECT(実際の値, 述語);
  • EXPECT_MSG(実際の値, 述語, メッセージ);
  • ASSERT(実際の値, 述語);
  • ASSERT_MSG(実際の値, 述語, メッセージ);

EXPECT()は条件が満たされなかった場合も以降のテストが継続されるのに対して、ASSERT()は即座にエラーの発生したテストを中断します *1

述語

現在のところ、値に対する述語として以下のようなものが用意されています。

  • is_true: actual == true
  • is_false: actual == false
  • is_null: actual == NULL
  • is_not_null: actual != NULL
  • eq(x): actual == x
  • ne(x): actual != x
  • lt(x): actual < x
  • le(x): actual <= x
  • gt(x): actual > x
  • ge(x): actual >= x
  • eq_str(s): actual == s
  • eq_str_n(s): actual[0..n] == s ※ このeq_str_n() のみ EXPECT((ptr, len), eq_str_n(s)) のように記述します。
  • contains(s): actual が s を含む
  • not(pred): 述語predを反転する

また、述語そのものはマクロや関数ではないため、以下のように同名の変数や関数が定義されていても問題なく動作します。

bool is_true = true;
EXPECT(is_true, is_true);

int eq = 42;
EXPECT(42, eq(eq));

グルーピング

GROUP() を使用することでテストコードをグルーピングすることが出来ます。

TEST(numeric) {
    GROUP("integer") {
        int n = 42;

        EXPECT(n, eq(42));
        EXPECT(n, ne(41));
        EXPECT(n, lt(43));
        EXPECT(n, le(42));
        EXPECT(n, gt(41));
        EXPECT(n, ge(42));
    }

    GROUP("floating point") {
        double f = 3.14159265;

        EXPECT(f, ne(0));
        EXPECT(f, lt(3.15));
        EXPECT(f, le(3.15));
        EXPECT(f, gt(3.14));
        EXPECT(f, ge(3.14));
    }
}

出力

CUTEは実行されたテストケースを出力します。 また、テストケースやグループごとに実行時間の計測結果を出力するため、簡単なベンチマークとしても利用できます。

もしテストの実行中にエラーが発生した場合、その周囲のアサーションなどを出力します。 問題のない成功ケースの出力を省略することでエラー箇所の把握がしやすいようになっています。

仕組み

CUTEの大半の機能はマクロを用いて実装されています。

メインの機能であるアサーションは以下のようなコードに展開されます。

ASSERT(x, eq(42));
    ↓
CUTE_ASSERT(x, eq(42));
    ↓
CUTE_ASSERT_I(CUTE_TESTING, x, CUTE_PRED_eq(42));
    ↓
CUTE_ASSERT_II(cute_testing, x, CUTE_PRED_eq_COND, CUTE_PRED_eq_DESC, 42);
    ↓
CUTE_ASSERT_III(cute_testing, CUTE_PRED_eq_COND(x, 42), CUTE_PRED_eq_DESC(x, 42));
    ↓
CUTE_ASSERT_III(cute_testing, (x == 42), "x == 42"));
    ↓
do {
  if (!cute_assert(cute_testing, (x == 42), "x == 42", __FILE__, __LINE__)) {
    return;
  }
} while (0);

また、グルーピングは以下のように展開されます。

GROUP("group %d", 42) { ... }
    ↓
CUTE_GROUP("group %d", 42) { ... }
    ↓
CUTE_GROUP_I(CUTE_TESTING, "group %d", 42) { ... }
    ↓
CUTE_GROUP_I(cute_testing, "group %d", 42) { ... }
    ↓
for (
  cute_testing_t *_cute_tmp = cute_testing,
                 *cute_testing = CUTE_GROUP_START(_cute_tmp, "group %d", 42);
  cute_testing;
  CUTE_GROUP_FINISH(cute_testing), cute_testing = NULL
) { ... }

まとめ

ヘッダーオンリーのシンプルな単体テストフレームワークを作成しました。

個人のCプロジェクトの単体テストに用いてみたところひとまず問題なさそうなので満足しています。

*1:このあたりの命名GoogleTest に倣っています

自作Zshプロンプトを非同期対応した

自作のシェルプロンプト、almelを非同期対応させることで巨大なリポジトリ内での硬直を軽減し、使用感を向上させました。

github.com

Almel とは

almel はRust製の高速に動作するシェルプロンプトです。 現在はBash, Zsh, fishに対応しています*1

以前の記事:

ryooooooga.hateblo.jp ryooooooga.hateblo.jp

Almelはlibgit2を用いることで高速にGitステータスを表示しています。

ですがこれまでは常に同期的にGitステータスを取得していたため、巨大なリポジトリ内では場合によって描画に〜1秒程度の時間がかかることがあり、発生する待ち時間がストレスになることがありました。

既存の非同期プロンプト

Powerlevel10kagkozak Zsh Promptなどの既存のZshプロンプトの中には、非同期に描画することでスループットを向上させているものがあります。

github.com github.com

これらは、Gitステータスなどの実行に時間の掛かるセグメントを除外した"仮のプロンプト"を同期的に表示し、その後Gitステータスを含む完全なプロンプトを非同期的に描画することでコマンド実行後の硬直を軽減し、利用体験を向上させています。

Almelの非同期対応

今回、Almelを非同期対応させることでこれらと同様に利用体験を向上させました*2

非同期描画にはzsh-asyncを利用しているため、あらかじめなんらかの方法でzsh-asyncをインストールしておく必要があります*3

github.com

初期化方法:

# .zshrc
# zsh-asyncを読み込む
source "${zsh-asyncのインストールディレクトリ}/async.zsh"

# Almelを初期化する
eval "$(almel init zsh --async)"

Zinitを用いた初期化方法:

# .zshrc
zinit light-mode from'gh-r' as'program' for \
    atload'eval "$(almel init zsh --async)"' @'Ryooooooga/almel'

zinit wait lucid blockf light-mode for \
    @'mafredri/zsh-async'

パフォーマンスについて

では、もともとGitに関する情報の取得にどの程度の時間がかかっていたのか測定してみます。

小さいリポジトリでの計測

まず比較的小規模であるAlmel自身のリポジトリを用いて、Gitリポジトリに関する情報取得の有無による実行速度の差を確認します。

# Gitステータスあり
$ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh >/dev/null; done)
( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh > /dev/null; done; )  1.16s user 0.71s system 83% cpu 2.248 total
# 平均 22.48ms (N=100)

# Gitステータスなし
$ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh --no-git >/dev/null; done)
( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh --no-git > /dev/null; ;   0.83s user 0.46s system 78% cpu 1.629 total
# 平均 16.29ms (N=100)

ここで利用している almel prompt コマンドはAlmelのプロンプトの実際の表示を行っているコマンドで、--no-git オプションを渡すことでGit関連の機能を全て無効化できます。

差はわずかではありますが、当然ながら--no-gitオプションありのほうが速いことがわかります。

Profiling Rust - macOS で DTrace を使って FlameGraph を描画する - Qiita を参考にプロファイリングを行ってみると、Gitステータスの取得 (下図 右 緑矩形) に最も時間が掛かり、次いで Gitリポジトリの初期化 (下図 左 青矩形) に時間が掛かっていることがわかります。

f:id:Ryooooooga:20220408185040p:plain
Almelのプロファイル結果 (青: Gitリポジトリの初期化, 緑: Gitステータスの取得)

Gitステータスの原理からして、リポジトリに含まれるファイルの数が多いほどより多くの時間が必要になります。

やや大きいリポジトリでの計測

続いて、手元にあったリポジトリの中で最も体感の描画時間が長かった lazygit を用いて計測を行います。

# Gitステータスあり
$ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh >/dev/null; done)
( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh > /dev/null; done; )  10.24s user 15.13s system 95% cpu 26.438 total
# 平均 264.38ms (N=100)

# Gitステータスなし
$ time (for i in {1..100}; do almel prompt -s0 -j0 -d1 zsh --no-git >/dev/null; done)
( for i in {1..100}; do; almel prompt -s0 -j0 -d1 zsh --no-git > /dev/null; ;   0.47s user 0.42s system 73% cpu 1.207 total
# 平均 12.07ms (N=100)

Gitステータスありの場合、260ms以上も掛かっていることがわかります。 これほどになると同期描画の硬直はストレスに感じます。

まとめ

Almelのプロンプトの描画を非同期化することで利用体験を向上させました。

以前からGitステータス取得のパフォーマンスの低下は懸念点であったため、それが解消できたことは喜ばしいです。

また、almel init zsh --asyncで出力されるスクリプト(下記)は非常に短く簡単であるため非同期プロンプトを実装する際の参考になるでしょう。

github.com

*1:fish用の出力を利用することで、がんばればPowerShellでも動かせることを確認しています。

*2:現在、非同期描画に対応しているのはZshのみです。

*3:zsh-asyncが読み込まれていない場合は通常通り同期的に描画されます。

Denoでcommitizen的なツールを作った

Denoでcommitizen互換のツールを作りました。

f:id:Ryooooooga:20220121200521p:plain

github.com

commitizenはGitのコミットメッセージをいい感じにするためのツールです。

commitizenのようなツールには公式である commitizen/cz-cli の他に、streamich/git-cz や Go製の lintingzhen/commitizen-go があります。

以前は cz-cli を使用していたのですが、これは Node.js 製であるため、グローバルに npm install する必要があります。 しかし、私はグローバルに npm install, gem install, pip install をあまりしたくないため、これに若干の不満がありました。

commitizen-go はNode.jsに依存しませんが、あまり開発が活発ではなさそうな上、入力にEmacsキーバインドが使用できないことから移行先としては若干心許ないように思えたため、似たようなものを自作することにしました。

  • commitizen-deno について
    • インストール方法
    • 使用方法
    • コンフィグ
  • なぜDenoを選択したか
  • なぜFZFを利用したか
    • 自由入力に FZF を用いる
    • コミットメッセージのプレビュー
  • まとめ
続きを読む