Tmuxinatorの代替ツールをDenoで作った

Tmuxinator の代替CLIツール qtmut *1 を作りました。

github.com

  • 1. これはなに
  • 2. 使い方
  • 3. なぜ自作したか
    • 3.1. 普段のTmuxの利用方法
    • 3.2. Tmuxinator の導入
    • 3.3. Tmuxinator と qtmut の思想の違い
  • 2. なぜDenoを選択したか
  • 3. まとめ

*1:qtμt というマンガから取りました。https://manga.line.me/product/periodic?id=Z0000153

続きを読む

Git 2.0.0から2.33.1までのDockerイメージを作った

Git 2.0.0から2.33.1までの各バージョンのDockerイメージを作りました。

hub.docker.com

github.com

Git のバージョンごとの挙動を検証したかったのですが、丁度いいDockerイメージが無かったので作りました。

ベースイメージは扱いやすいようにUbuntuを採用し、マルチステージビルドを活用することで1つあたりの容量が小さくなっています (約130MB)。

イケてないところ

イメージのビルドをローカルマシン上で行っています。 1バージョンあたり2~4分かかるため、全191バージョンで7時間かかりました。

文脈を指定可能な自動展開されるglobal aliasを実現するZSHプラグイン zabrze を作った

ZSHにはglobal aliasという機能があります。 これは、行頭以外に入力された場合にも展開されるaliasです。

$ alias -g B='"$(git symbolic-ref --short HEAD)"'
$ git push -u origin B
# → git push -u origin [現在のブランチ名]

使いこなせれば便利そうな一方で、global aliasはグローバル名前空間を汚染するため、導入には慎重さを要します*1

また、コマンドヒストリーには通常のaliasと同様にglobal aliasの展開前の入力が保存されます。

これら2つの問題点を解決することを目的に、zabrze というZSHプラグインを作成しました。

github.com

これはなに

zabrze *2 は aliasをYAMLで記述できるようにするZSHプラグインです。

1. aliasの自動展開

例えば下のような設定ファイルを ~/.config/zabrze/config.yaml に記述した場合、行頭で g<スペース> と入力したとき、即座に git と展開されます *3

また、null<スペース> と入力した場合は行頭以外でも >/dev/null に展開することができます。

# ~/.config/zabrze/config.yaml
abbrevs:
  # alias
  - name: git
    abbr: g
    snippet: git

  # global abbrev
  - name: '>/dev/null'
    abbr: null
    snippets: '>/dev/null'
    global: true

2. 文脈を指定した global alias

さて、ここまでであれば後述する既存のZSHプラグインによってすでに同様の機能が実現されています。

zabrze ではaliasの自動展開に加え、展開の有無をその文脈によって切り替えられるようになっています。

  # git コマンドを入力しているときのみ展開されるglobal alias
  - name: branch
    abbr: B
    snippet: $(git symbolic-ref --short HEAD)
    evaluate: true # snippetの文字列をスクリプトとして解釈する
    global: true
    context: '^git ' # 展開される文脈を正規表現で指定する

上のような global alias B は、git コマンドを入力しているときのみ展開されます。

$ git push -u origin B<エンター>
# ↓ に展開され、実行される
# git push -u origin [現在のブランチ名]

$ echo B<スペース>
# ↑ は展開されない 

これにより、global alias自体の有用性を保ったままグローバル名前空間の汚染を避けることができます。

類似のZSHプラグイン

zsh-abbrev-alias

github.com

qiita.com

zsh-abbrev-alias は自動展開される alias コマンド、abbrev-alias コマンドを追加するシンプルなプラグインです。

zeno.zsh

github.com

zenn.dev

zeno.zsh は Deno 製のZSHプラグインです。 abbrev-alias相当の機能に加え、pmyのような補完機能を有しています。

一方、実行にはDenoのインストールを要求し、またスペースを押したときの動作が重い印象があります (おま環かも)。

開発のモチベーション

以前は zsh-abbrev-alias を利用していましたが、Gitなどのサブコマンドにabbrev-aliasを適用できないことを不満に思っていました。

$ abbrev-alias g=git
$ g<SP>  # git に展開される

$ abbrev-alias -g c=commit
$ git c<SP>  # git commit に展開されるがグローバル名前空間の汚染がきびしい

$ git config alias.c commit
$ git c  # history には `git commit` で記録されてほしい

現在は以下のような zabrze の config.yaml によって Git aliases の自動展開を実現しています。

github.com

*1:global aliasの活用例で紹介されるものは1~4文字程度の短いものが多い印象があります (長いglobal aliasの利点は少ないため)

*2: z (ZSHの頭文字) + abbr でそれっぽい単語がないか探した結果の命名です。期せずして almel, zouch と同じく地名になりました

*3:スペースではなく、エンターキーを入力した場合も同様に展開されます

Sublime Text 4で追加された設定など

昨日 (2021/05/21)、Sublime TextのメジャーアップデートであるSublime Text 4がリリースされました。

www.sublimetext.com forest.watch.impress.co.jp

デフォルトのカラースキームがMonokaiからMarianaに変わったり、Open GLによるGPUアクセラレーションが有効になったりしましたが、 他にもちょっとした設定項目の追加などがあったため、便利だと感じたものを紹介します。

目次

互換性

Sublime Text 4は、メジャーアップデートではあるものの設定ファイルやパッケージなどの互換性は維持されています。 私のインストールしている以下のようなパッケージは特に問題なく動作しています。

{
    "bootstrapped": true,
    "in_process_packages":
    [
    ],
    "installed_packages":
    [
        "A File Icon",
        "AdvancedNewFile",
        "All Autocomplete",
        "BracketHighlighter",
        "Case Conversion",
        "Clang Format",
        "CMake",
        "DoxyDoxygen",
        "EditorConfig",
        "FindKeyConflicts",
        "Git",
        "GitGutter",
        "Increment Decrement",
        "Increment Selection",
        "LSP",
        "Origami",
        "Package Control",
        "PackageResourceViewer",
        "ProjectManager",
        "SideBarEnhancements",
        "Smart Delete",
        "Sublime Files",
        "SublimeGHQ",
        "SyncedSideBar",
        "TodoReview",
        "TOML"
    ],
    "repositories":
    [
        "https://github.com/Ryooooooga/SublimeGHQ"
    ],
}

また、macOSの場合設定ファイルの保存先は ~/Library/Application Support/Sublime Text 3/ から ~/Library/Application Support/Sublime Text/ に変更されました。 ただし、~/Library/Application Support/Sublime Text/ が存在しない場合は ~/Library/Application Support/Sublime Text 3/ 以下の設定が読み込まれるため、 移行にあたっての対応は特に必要ありません。

オートコンプリートの強化

オートコンプリート機能の刷新に伴ってオートコンプリートに関する設定項目が複数追加されました。

f:id:Ryooooooga:20210522134143p:plain
Sublime Text 4の補完候補リスト

使用感としてはSublime Text 3とほぼ変わりがないものの、補完候補の種類 (スニペット、関数、型など) に対応したアイコンが表示されるようになり、単純にわかりやすくなりました。 また、補完候補の定義へのジャンプなどが可能になりました。

f:id:Ryooooooga:20210522135214p:plain
Sublime Text 3の補完候補リスト

便利な設定の追加

Sublime Text 4になり、いくつか設定項目が追加されました。 うち、特に便利だと感じたものを紹介します。

draw_white_space のカスタマイズ性の向上

draw_white_space は空白文字の可視性を設定するための項目です。

draw_white_space 自体は Sublime Text 3から存在していましたが、以下の3種類の設定のいずれかしか指定できませんでした。

  • "none": 空白文字を表示しない
  • "all": すべての空白文字を常に可視化する
  • "selection": 選択範囲のみ空白文字を可視化する

Sublime Text 4 ではより複雑な可視性の制御が行えるようになりました。

Sublime Text 4のデフォルトのdraw_white_space

  // Controls when white space is drawn. Any of the following options may be
  // combined:
  // - "selection": Draw white space under the current selection.
  // - "leading": Draw any white space between the beginning of a line and the
  //              first character.
  // - "enclosed": Draw white space enclosed by other characters.
  // - "trailing": Draw white space following the last character on a line.
  // - "isolated": Draw white space on lines containing no other characters.
  // - "all": All of the above, ie. always draw white space.
  //
  // These options may be further refined by appending any of the following
  // separated by an underscore:
  // - "none": Don't draw this kind of white space.
  // - "tabs": Only draw tabs here.
  // - "spaces": Only draw spaces here.
  // - "mixed": Only draw white space that does not match the indentation
  //            style. For example if "translate_tabs_to_spaces" is true only
  //            draw tabs.
  // - "mixed_tabs": Like "mixed" but only draw tabs.
  // - "mixed_spaces": Like "mixed" but only draw spaces.
  // - "all": Draw both tabs and spaces. This is the default.
  //
  // Note that options are applied in sequence. So a later option may override
  // an earlier one.
  //
  // Examples:
  // - ["selection", "trailing", "isolated"]:
  //     Draw white space at the end of any lines and under the selection.
  //
  // - ["all_tabs", "selection"]:
  //     Draw tabs anywhere and any white space under the selection.
  //
  // - ["all_mixed"]:
  //     Draw any white space that does not match the indentation style.
  //
  // - ["leading_mixed", "isolated_mixed"]:
  //     Draw any indentation that does not match the indentation style.
  //
  // - ["selection_mixed_tabs"]:
  //     Draw only tabs under the selection and only if the indentation style
  //     is spaces.
  //
  // - ["all_tabs", "selection"]:
  //     Draw all tabs and any white space under the selection.
  //
  // - ["all", "selection_none"]:
  //     Inverse of the default. Draw white space everywhere except under the
  //     selection.
  "draw_white_space": ["selection"],

例えば、私の場合は以下のように設定しています。

   "draw_white_space": [
        "all_tabs",  // すべてのタブ文字を表示する
        "selection", // 選択範囲の空白文字を可視化する
        "trailing",  // 行末の空白文字を可視化する
    ],

ただし、設定の記述順によって表示が変わったり、そもそもの指定方法がわかりにくかったりとなかなかに難解です。(ちょっとだめなところがSublime Textっぽくてかわいいね)

"ignored_snippets" の追加

"ignored_snippets"Sublime Text 4から追加された、スニペットを無効化するための項目です。

Sublime Text 4のignored_snippets

   // A list of wildcard patterns specifying which snippet files to ignore.
    // For example, to ignore all the default C++ snippets, set this to
    // ["C++/*"]
    "ignored_snippets": [],

Sublime Text 3でデフォルトで組み込まれているパッケージ (C++パッケージなど) のスニペットを無効化したい場合、 PackageResourceViewer などを使ってデフォルトスニペットファイルを抽出し、 それらのスニペットファイルの中身を削除する などの作業が必要でした。

Sublime Text 4では、"ignored_snippets"スニペットのファイル名を追加することで無効化できるようになりました。(なにげにこれが一番うれしかった)

   // デフォルトのC++スニペットをすべて無効化する
    "ignored_snippets": [
        "C++/*",
    ],

"hardware_acceleration" の追加

"hardware_acceleration"GPUによるハードウェアアクセラレーションの有効化に関する設定項目です。 デフォルトではmacOSでのみOpenGLによるハードウェアアクセラレーションが有効になっています。 

Sublime Text 4のhardware_acceleration

   // Enables hardware accelerated rendering. This moves rendering to your GPU,
    // allowing for faster rendering at higher resolutions. Changing this
    // setting requires an application restart to take effect.
    // - "none": Performs CPU rendering.
    // - "opengl": Uses OpenGL for rendering. Minimum required version is 4.1
    //
    // On Mac, this value is overridden in the platform specific settings.
    "hardware_acceleration": "none",

"relative_line_numbers" の追加

"relative_line_numbers"true に設定すると、行番号が相対表示になります。 (Vimset relativenumber のような感じ)

Sublime Text 4のrelative_line_numbers

   // relative_line_numbers will draw each line number as the distance from
    // the current line. Useful in conjunction with Vintage.
    "relative_line_numbers": false,

ST4のbeta版を使っていた身としては正式リリースは嬉しい限りですが、いくらなんでも遅すぎませんかね? 日本でSublime Text使ってる人はもはや私しかおらないぞ。 

Dockerでカスタムsubcommandをあれする

Gitでは git-foo のような名前のスクリプトファイルを PATH の通っている位置に置いておくと、 git foo とするだけで呼び出せる便利な仕組みがあります。

本記事の趣旨はDockerでもそんな感じのことをしたいよね。というあれです。

実装

で、以下のような関数を .zshrc なりに書いて置くとGitのような感じでsubcommandを探索してくれます。

docker() {
    if [ "$#" -eq 0 ] || ! command -v "docker-$1" > /dev/null; then
        command docker "$@"
    elif (( ${+aliases[docker-$1]} )); then
        eval "${aliases[docker-$1]} ${(q)@:2}"
    else
        "docker-$1" "${@:2}"
    fi
}

この関数 docker を用いて docker foo のようにサブコマンドを呼び出した場合、

  • docker-foo」のようなコマンドやエイリアスが存在していない場合は生のdocker コマンドに引数を渡す
  • docker-foo というエイリアスが存在していれば docker-foo に残りの引数を渡す1
  • docker-foo というスクリプトか関数が存在していれば docker-foo に残りの引数を渡す

というような感じであれしてくれます。

自分は以下のようなエイリアスを使っています。

# docker run のエイリアス 
alias docker-ri='command docker run -it'
alias docker-rrm='command docker run --rm'
alias docker-rrmi='command docker run --rm -it'

# exitedなプロセスを全部 rm する
docker-clean() {
    command docker ps -aqf status=exited | xargs -r docker rm --
}
# タグ付けされていないイメージを全部 rmi する
docker-cleani() {
    command docker images -qf dangling=true | xargs -r docker rmi --
}

# 引数なしで docker rm すると削除するプロセスを fzf で選択できる
docker-rm() {
    if [ "$#" -eq 0 ]; then
        command docker ps -a | fzf --exit-0 --multi --header-lines=1 | awk '{ print $1 }' | xargs -r docker rm --
    else
        command docker rm "$@"
    fi
}

# 引数なしで docker rmi すると削除するイメージを fzf で選択できる
docker-rmi() {
    if [ "$#" -eq 0 ]; then
        command docker images | fzf --exit-0 --multi --header-lines=1 | awk '{ print $3 }' | xargs -r docker rmi --
    else
        command docker rmi "$@"
    fi
}

余談

それでいいのか?


  1. git はシェルエイリアスの探索はしてくれませんが、git config alias.foo の代替みたいな気持ちです

テンプレートから新規ファイルを作成するCLIツール zouch を作った

例えば新しいプロジェクトを作成するとき、何かと新しいファイルを作成する必要があります。

README.md, LICENSE, .editorconfig, あるいはC/C++ならCMakeLists.txtやmain.cppなどをプロジェクトに含める必要があるでしょうが、 そのうちいくつかは毎度似たようなファイルを「お約束」として用意することでしょう。

私の場合はそれらを既存のプロジェクトからコピーしてきたり、もしくは touch で空のファイルを作成したあとにVSCodeSublime Textといったエディタのスニペット機能を用いて中身を入力することが多いです。

しかし、touch して〜、エディタで開いて〜、というのも結構めんどくさいので touch した時点でファイルの中身も自動生成してくれればなにかと便利そうです。

そこで、zouchというCLIツールを作成しました。

github.com

これはなに

zouchを使用すると、予め登録しておいたテンプレートからファイルを作成できます。

$ cat LICENSE.md
cat: LICENSE.md: No such file or directory

$ zouch LICENSE.md

$ cat LICENSE.md
# MIT License (MIT)

Copyright (c) 2021 Ryooooooga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
...

上のようなMITライセンスのテキストファイルは、~/.config/zouch/templates に配置された以下のようなテンプレートから作成されます。

# MIT License (MIT)

Copyright (c) {{Now.Year}} {{Shell `git config user.name`}}

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
...

zouchではGo言語のテンプレートエンジンである text/template の記法を用いて出力内容をある程度動的に変更できるようになっています。 上の例で言うと {{ ... }} に囲まれている部分 (西暦N年、ユーザ名) が出力時にはいい感じに展開されているのがわかると思います。

また、touch 本来の機能であるタイムスタンプの更新もできるようになっています。1

テンプレートファイルの探索

zouchでは /path/to/foo.txt というファイルを生成する際にまず ~/.config/zouch/templates/foo.txt というファイルを探し、 これが存在していればそれをテンプレートファイルとして用います。

foo.txt~/.config/zouch/templates/ 以下に存在しないときは、代わりに _.txt という名前のファイルを探し、これを代わりにテンプレートファイルとして用います。

これによって、"特定の拡張子のファイルはすべて同じテンプレートから生成したい" という要求を解決します。 以下に例として、.goファイルのテンプレートを示します。

{{ if HasSuffix .Filename "_test.go" -}}
package {{.Filename | Abs | Dir | Base}}_test

import (
    "testing"
)

func Test{{RegexReplaceAll .Filename `_test\.go$` "" | Base | UpperCamelCase}}(t *testing.T) {
}
{{ else -}}
package {{.Filename | Abs | Dir | Base}}
{{ end -}}

_.go の例

このテンプレートは、foo/bar.go というファイルを生成しようとした場合は

package foo

というファイルを、foo/bar_test.go というファイルを生成しようとした場合は

package foo_test

import (
    "testing"
)

func TestBar(t *testing.T) {
}

というファイルをそれぞれ出力します。

インストール方法

  • go get
$ go get github.com/Ryooooooga/zouch
  • zinit (プリビルトバイナリのダウンロード)
# ~/.zshrc
zinit ice lucid wait"0" as"program" from"gh-r" \
    pick"zouch*/zouch"
zinit light 'Ryooooooga/zouch'

類似のツール

類似のツールに touch-alttouch_erb があります。

touch-alt

github.com

qiita.com

touch-alt は Node.js 製のCLIツールです。 テンプレートエンジンなどは使用せず、単にテンプレートファイルをコピーしてくるだけのシンプルなツールです。

touch_erb

github.com

touch_erb は Ruby 製のCLIツールです。 ERBを採用することで柔軟なファイルの生成が可能になっています。

zouchはこのtouch_erbから着想を得て (というか丸パクし) 作成しました。

touch_erbを用いず、わざわざ自作したのは以下のような理由のためです。

  • dotfilesRubyのインストールを前提としていない
  • 宗教的な問題でグローバルに gem install したくない2

zouch の作成にあたって

zouchの実装言語にはGoを選択しました。 以下のような言語も選択肢には入っていましたが、シングルバイナリにコンパイルできること、 テンプレートエンジンの言語標準として text/template ライブラリが存在していることが選定の決め手となりました。3

  • Shellスクリプト: 処理系のインストールが不要 / 書くのが辛い
  • Perl: 処理系のインストールが不要 / わからない
  • Node.js: dotfiles内でインストールしている / npm i -gしたくない
  • Python: dotfiles内でインストールしている / pip install したくない, 書きたくない
  • C++ (+ Luaなどの組み込みスクリプト): シングルバイナリにコンパイルできる / 実装コストが重い
  • Rust: シングルバイナリにコンパイルできる / テンプレートエンジンとして標準的なものが存在しない

一方、テンプレート内で任意のRubyスクリプトを書けるERBとは違い、 Goのtext/templateの表現力は極めて限定的です。 例えば、テンプレート内で使用できる関数は (素朴な実装に於いては) コンパイル時に登録されたものに限られます。

そこで、Shell関数を通じて任意のシェルスクリプトを実行できるようにすることで表現力の低さを補うことにしました。


テストを書きやすいようにパッケージの分割を工夫したりはしたものの、この手のCLIツールはテストを書くのがすごく面倒です。 あとドキュメントを書くのも面倒。

それからGoの time.Time.Format() の仕様を考えた人は早く謝罪の言葉を考えたほうがいい。


  1. touchに付いているオプションなどは実装していないため完全な上位互換として用いることはできませんが、大雑把な方なら alias touch=zouch してもある程度大丈夫だと思います。多分。

  2. pip installもしたくない。npm install -g もできればしたくない。

  3. 個人的にGo言語は好きでない。 (C言語を書いている気持ちになるため)

fzf+pmyでzshの補完をカスタマイズする

最近 relastle/pmy というツールにハマっています。

Zshの補完機能の欠点

zshの補完機能は強力ですが、カスタマイズ性や拡張の容易さに難があります。

また、やろうとおもえば補完関数内でfzfなどのFuzzy Finderを用いてインタラクティブな補完を実現できますが、fzfの--multiオプションなどを用いた複数結果の展開などはできません。

デフォルトの補完機能 (ZLEのexpand-or-complete)を上書きするような形でそれらも実現はできますが、やはり記述の複雑さや保守性の面からつらいものがあります。

そこで、pmyというCLIツールとfzfを組み合わせてインタラクティブな補完を実現しました。 利用時の様子は上のツイートのとおりです。

fzf

fzfはGo製のFuzzy Finderです。

github.com

日本では同じくGo製のFuzzy Finderであるpecoの方が知名度が高い印象がありますが、preview windowはpecoにはない魅力です。

f:id:Ryooooooga:20201231130448p:plain
fzfのpreview window

preview windowは、任意のコマンドなどを用いて検索候補のプレビューを表示できる機能です。 上の図ではbatを用いてシンタックスハイライト付きでファイルの中身を表示しています。他にもlstreeのようなコマンドを使ってディレクトリの構造を表示する、あるいはgit showでコミットの情報を表示するなど、用途の幅が広い便利な機能です。

私の場合は以下のような用途にfzfを用いていました。

  1. historyを使ったコマンドヒストリーの検索
  2. chpwd_recent_dirsを使ったディレクトリ移動履歴の検索
  3. ghqを使ったリポジトリ間の移動
  4. テキストエディタを開く際のファイルの選択
  5. docker rmdocker rmiの際に削除するイメージの選択

1 ~ 3 はFuzzy Finderの利用例で頻繁に挙げられる用途なので特筆すべきことはありません。

4 に関しては以下のような$EDITOR (私の場合はnvim) のラッパー関数 e を用意していました。

e() {
    if [ $# -eq 0 ]; then
        local selected="$(fd --hidden --color=always --exclude='.git' --type=f  | fzf --exit-0 --multi --preview="fzf-preview-file '{}'" --preview-window="right:60%")"
        [ -n "$selected" ] && "$EDITOR" -- ${(f)selected}
    else
        command "$EDITOR" "$@"
    fi
}

これは、eに引数が指定されていた場合は通常通りに $EDITOR を起動し、引数なしで呼び出された場合にはカレントディレクトリ以下のファイルから fzf で選択したものを $EDITOR の引数に与えるものです (かなり便利)。

これまでの不満点

上のような fzf の活用は、実際に便利で重宝していたのですが、若干の物足りなさを感じていました。 具体的には以下のような点です。

  • e で選択したファイルがコマンドヒストリーに記録されない

    上の関数ではどのようなファイルを選択してもコマンドヒストリーに e しか残りません。 そのため、以前に開いたファイルを再び開こうと思った場合にも再度検索を行わなければならず、煩わしさを感じていました。

  • git switch などの既存のコマンドの補完にfzfを用いたい

    Gitなどの既存のコマンドやそのサブコマンドの補完にfzfを用いたい場合、逐一ラッパースクリプトを用意しなければならず面倒でした。

余談

fzfには補完用のスクリプトが用意されており、これを使用することでfzfを使った補完が可能になっていました。

参考: https://github.com/junegunn/fzf#fuzzy-completion-for-bash-and-zsh

また、この機構を利用してGitなどの補完を実現したプロジェクトに chitoku-k/fzf-zsh-completions があります。

github.com

今回、これらのような既存のものを利用しなかったのは以下の理由のためです。

  • 自分用の環境に合わせてカスタマイズするのが面倒
  • 補完のトリガーである**<TAB>というシーケンスに慣れない

pmy

というわけで本題です。

pmyはFuzzy Finderを用いたzshの補完を実現するためのCLIツールです。

github.com qiita.com

YAML (もしくはJSON) で設定ファイルを書くことにより、冒頭で示したようなfuzzyな検索と補完が可能になります。

インストール方法

  • go get を用いたビルド方法
$ go get -u github.com/relastle/pmy
$ eval "$(pmy init)"
  • zinit を用いたプリビルドバイナリのダウンロード
zinit ice lucid wait"0" as"program" from"gh-r" \
    pick"pmy*/pmy" \
    atload'eval "$(pmy init)"'
zinit light 'relastle/pmy'

設定ファイルの編集

pmyは、デフォルトでは ~/.pmy/rules 以下に配置された設定ファイルを参照します。 例えば、冒頭のツイートのような補完は下の設定で再現できます。

# ~/.pmy/rules/_.yaml
- description: editor
  regexp-left: ^\s*(?P<cmd>(nvim|vim|vi))\s+(?P<args>\S+\s+)*)(?P<query>\S*)$
  cmd-groups:
    - stmt: fd --color=always --hidden --type=f
      after: paste -s -d ' ' -
  fuzzy-finder-cmd: fzf --multi --preview="bat {}" --query=<query>
  buffer-left: '<cmd> <args>'
  buffer-right: '[]'

f:id:Ryooooooga:20201231173125p:plain
pmyによるファイル名補完の例

上の場合、ファイルパスやその一部を雑に入力しただけで候補に出現するため、通常のzsh補完よりも効率的に目的のファイルにたどり着けます。

他にも以下のような設定でpmyを運用しています。

https://github.com/Ryooooooga/dotfiles/tree/main/config/pmy/rules

f:id:Ryooooooga:20201231173324p:plain
pmyによるGitサブコマンド補完の例

設定ファイルの参照ディレクトリなどの変更

pmy はデフォルトでは ~/.pmy 以下を作業ディレクトリとして用います。 しかし、私はホームディレクトリが雑多なdotfilesで汚れることをあまり好ましく思っていません。

幸いにも、pmyは環境変数によって作業ディレクトリを変えられるため、XDG風のファイルの配置に変更しました。 また、補完のトリガーになるキー(デフォルトではCtrl+Space)も変えられます。

# 補完のトリガーを変更 (Ctrl+P)
export PMY_TRIGGER_KEY="^P"

# ルールファイルの参照先を変更
export PMY_RULE_PATH="$XDG_CONFIG_HOME/pmy/rules"
export PMY_SNIPPET_PATH="$XDG_CONFIG_HOME/pmy/snippets"
# ログファイルの出力先を変更
export PMY_LOG_PATH="$XDG_CACHE_HOME/pmy/log.txt"

eval "$(pmy init)"

pmyの利点

pmyを利用することには以下のような利点があります。

  • コマンドやサブコマンドなどのコンテキストごとに補完の候補を柔軟に変えられる
  • YAMLなのでShellスクリプトに比べカスタマイズが容易
  • 補完結果がコマンドヒストリーに残る

pmyの欠点

一方で、pmyもまた万能ではありません。 しばらく利用してみて感じた欠点を以下に挙げます。

  • ルールのマッチングのための正規表現を書くのがつらい
    • 空白や引用符などを考慮した正規表現を書こうとすると大変
    • オプションやフラグごとの補完候補の変更など、あまり複雑なことをしようとすると正規表現が爆発する
  • ルールのマッチングは上から順に判定されるため、設定の記述順序が重要になる
  • エスケープシーケンス周りの動作が怪しい
    • PRを投げて幾分かはマシになった

pmyはかなり独特でおもしろいツールなので、fzfがすきなzshユーザの方は一回試してみるといいんじゃないでしょうか。