【Zsh】続・大改造ターミナル環境 〜zeno.zshでCLI操作を快適にする〜【Zinit】

:::note info
前回の記事
[【Zsh】大改造ターミナル環境【Zinit】 | Qiita](https://qiita.com/obake_fe/items/c2edf65de684f026c59c)
:::

上記記事にまとめたようにZshとZinitを使用してターミナル環境整えていたのですが、
さらなる環境向上のために便利なライブラリなどないかなーと調べてみたところ、
[zeno.zsh](https://github.com/yuki-yano/zeno.zsh)という大変使いやすく便利なライブラリを見つけました。

本記事はzeno.zshのインストール方法と使い方の紹介記事になります。

※zeno.zsh作者様の記事
[Deno + TypeScriptでzshプラグインを実装して最高になった](https://zenn.dev/yano/articles/zsh_plugin_from_deno)


# インストール手順
zeno.zshの利用には、[Deno](https://deno.land/)と[fzf](https://github.com/junegunn/fzf)のインストールが必須となります。
MacであればHomebewからインストールできます。

```shell
$ brew install deno
$ brew install fzf
```

次にzeno.zshの設定をします。
Zsh用プラグインマネージャであるZinitを使用していれば、`.zshrc`などのファイルに以下設定記述を書くだけで大丈夫です。

```~/.zshrc
## zeno設定
zinit ice lucid depth"1" blockf
zinit light yuki-yano/zeno.zsh

if [[ -n $ZENO_LOADED ]]; then
  # ここに任意のZLEの記述を行う
  bindkey ' '  zeno-auto-snippet
  bindkey '^m' zeno-auto-snippet-and-accept-line
  bindkey '^i' zeno-completion
  bindkey '^g' zeno-ghq-cd
  bindkey '^r' zeno-history-selection
  bindkey '^x' zeno-insert-snippet
fi
```

これでzeno.zshの基本的な設定は完了です。
あとは次に`.zshrc`を読み込むタイミングでzeno.zshが自動でインストールされます。

:::note info
zeno.zshのload後にZLE（Zsh Line Editor）のキーバインドを行うことで、任意のコマンドでzeno.zshの機能を実行することができます。
:::

# 使い方

ここからはzeno.zshの便利な機能とその使い方について見ていきましょう。

## Abbrev snippet
簡単にいうと略語展開です。
別ファイル（`config.yml`）に設定しているkeyword入力後にスペースキーを押すと、そのkeywordに設定しているsnippetが展開されます。

```config.yml
# configファイル内で事前に設定しておく
snippets:
  - name: git status
    keyword: gs
    snippet: git status --short --branch
```

```shell:コマンド実行
$ gs<Space>  # gsと入力後にスペースキーを押す

# ↓ 事前に設定しているコマンドが展開される

$ git status --short --branch
```
※gif
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/8c12df90-0188-57a5-9ecf-cd7064b22fde.gif)
keyword入力後にスペースキーを押すとコマンドが展開され、Enterキーを押すと展開と同時に実行されます。

一見シェルのalias機能に似ていますが、略形ではなくsnippet全体がコマンド履歴に残ることなどのメリットがあり、後述する様々な展開方法を使用することでさらに優れた機能を実現できます。

:::note info
config.ymlは`~/.config/zeno/config.yml`のように配置したり、
任意で環境変数`ZENO_HOME`を設定して`$ZENO_HOME/config.yml`などに配置することができます。

※詳しくは[README](https://github.com/yuki-yano/zeno.zsh#user-configuration-file)を参照してください。
:::

### 行頭以外での展開
`global: true`のオプションを設定することで、コマンドの途中からでもkeywordを展開することができます

```config.yml
snippets:
  - name: "null"
    keyword: "null"
    snippet: ">/dev/null 2>&1"
    global: true
```
```shell
$ ls null<Space>

# ↓ 行頭以外でもsnippetが展開される

$ ls >/dev/null 2>&1
```

### 正規表現による展開
`context`内の`lbuffer`オプションを使用することで、展開を行う条件を正規表現で指定することができます。

```config.yml
snippets:
  - name: git switch
    keyword: s
    snippet: switch
    context:
      lbuffer: ^git\s  # gitを入力している時のみ展開する
```
```shell
$ git s<Space>

# ↓ snippetが展開される

$ git switch
```
```shell
$ echo s

# ↓ snippetが展開されない

s
```

### placeholderを用いた展開

snippet内に`{{任意のテキスト}}`を入れ込むことで、略語展開後に指定したplaceholderの位置にカーソルを移動することができます。

```config.yml
snippets:
  - name: git commit message
    keyword: gcm
    snippet: git commit -m '{{commit_message}}'
```

```shell
$ gcm<Space>

# ↓ snippetが展開され、カーソルがplaceholderの位置まで移動した状態になる

$ git commit -m '（ここにカーソルが移動する）'   # カーソル移動をすることなくコミットメッセージを入力できる
```

### シェルスクリプトを実行した展開

`evaluate: true`のオプションを設定することで、snippetをシェルスクリプトとして実行した結果を展開することができます。

```config.yml
snippets:
  - name: branch
    keyword: B
    snippet: git symbolic-ref --short HEAD  # カレントブランチ名を取得するコマンド
    context:
      lbuffer: '^git\s+checkout\s+'
    evaluate: true
```
```shell
$ git checkout B<space>

# ↓ シェルスクリプト実行の結果が展開される

$ git checkout main
```

## Insert snippet
登録したsnippetをインタラクティブに検索しながら挿入することができます。
これを利用すればkeywordやsnippetを完全に覚えていなくても、検索から見つけて実行できるので大変便利です。

```~/.zshrc
# 「Ctr + x」にキーを設定
bindkey '^x' zeno-insert-snippet
```
※snippetを検索して選択するgif
![ezgif.com-gif-maker (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/af0909fd-2521-4d27-35c1-14f5b23013d7.gif)

## Search history
historyファイルに保存されているコマンド履歴を検索できる機能です。

```~/.zshrc
# 「Ctr + r」にキーを設定
bindkey '^r' zeno-history-selection
```
※コマンド履歴から検索して選択するgif
![ezgif.com-gif-maker (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/d525f471-c2d8-295c-04f5-2bfc422e8082.gif)


## Change ghq managed repository
リポジトリ管理ツールである[ghq](https://github.com/x-motemen/ghq)の機能を使って、Gitリポジトリ間の移動を簡単にする機能です。

```~/.zshrc
# 「Ctr + g」にキーを設定
bindkey '^g' zeno-ghq-cd
```
※リポジトリ一覧から検索してカレントディレクトリを切り替えるgif
![ezgif.com-gif-maker (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/92925829-60db-6fe9-328e-b9e718a92684.gif)


## Fuzzy completion
zeno.zshでは任意のコマンド（正規表現）に対して、fzfを用いたインタラクティブな補完を設定できます。
デフォルトで[いくつかのGitコマンド](https://github.com/yuki-yano/zeno.zsh#builtin-completion)の補完が設定されています。

```shell
# 設定したコマンド入力後にTabキーを押下
$ git switch <Tab> 
```
※git switchコマンドの補完でブランチ一覧から任意のブランチを選択するgif
![ezgif.com-gif-maker (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/8219d1c8-8b75-d1aa-b0e5-7c0f250e10be.gif)


## その他設定
### シンタックスハイライト
zeno.zshはzshのシンタックスハイライトプラグインである[zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting)と相性が良くないらしく、代わりに[fast-syntax-highlighting](https://github.com/zdharma-continuum/fast-syntax-highlighting)をインストールしました。

```~/.zshrc
zinit light zdharma-continuum/fast-syntax-highlighting
```


### fzfの表示設定
デフォルトでは検索画面の表示が画面下部に表示されていたため、画面上部に表示するよう見た目の変更をしました。
```~/.zshenv
export FZF_DEFAULT_OPTS='--reverse'
```

# まとめ
デフォルトの機能だけでも便利ですが、スクリプトを作ることで個人の使い方に合わせたカスタマイズもできそうなので、これからどんどんアレンジしていきたいです。
また、コマンドのインタラクティブ検索などは[peco](https://github.com/peco/peco)を利用して同様のことができていたのですが、レイアウトや使い勝手がfzfの方が好みでこちらも満足しています。

興味を持った方は是非使ってみてください。

# 参考
- [Deno + TypeScriptでzshプラグインを実装して最高になった](https://zenn.dev/yano/articles/zsh_plugin_from_deno)
- [dotfilesのこだわりを晒す - エムスリーテックブログ](https://www.m3tech.blog/entry/dotfiles-bonsai)