[fish-shell] [10] プラグイン実装例「zf」... 対話形式でのファイルランチャ


## 機能概要

コマンドラインにて「zf パス」として実行すると、fzf によって
対話的に開きたいファイルを選択する.

但し、使用するアプリは nvim 決め打ちとする.

なお、一度開いたファイルは繰り返し開く傾向にあるので、
fish の HISTORY にマージするようにしている.

fish-shell V2.7.1 で動作確認済み.

## 使用方法

下記コードを $HOME/.config/fish/functions/zf.fish として配置すること.

また、fzf をインストールしておくこと.

## コード

```bash
#!/usr/bin/env fish

function zf
    # 独自関数: fish のコマンド履歴ファイル (fish_history) に対して、
    #           本スクリプトで実行したコマンド (nvim 〜 か sudo nvim 〜) を追記する.
    function _add_history
        # コマンド実行履歴ファイルがあるかチェックする.
        # ここでは「/home/foo」としているので適宜読み替えること
        set -l hist_file /home/foo/.local/share/fish/fish_history
        if test ! -f "$hist_file"
            # fish_history ファイルが無ければ作成する
            touch $hist_file
        end

        # 現在実行したコマンドが fish_history ファイルにすでに存在しているかチェックする
        set -l pattern ( grep -w "\- cmd: $argv[1]" $hist_file )

        # 現在実行したコマンドがこれまでに存在していなければ追加する
        if test -z "$pattern"
            # 次のような書式
            echo "- cmd: $argv[1]"   >> $hist_file  # 本例では $argv[1] の値は「nvim」である
            echo "  when: $argv[2]"  >> $hist_file  # 本例では $argv[2] の値は ファイルパスである
            history merge # fish 組込みコマンド「history merge」を実行すると反映される
        end
    end

    #------------------------------------------------------------
    # ここから本処理開始
    #------------------------------------------------------------
    set -l filename
    set -l timestr (date "+%s")

    # zf コマンド実行時に引数が指定されていない場合
    if test (count $argv) -eq 0
        # カレント直下のファイルを fzf で表示させて、ユーザに選択させる
        set filename ( find . -type f 2>/dev/null | fzf )
        if test -n "$filename"
            if test -r "$filename"
                # これから実行するコマンドを fish_history に書き込んでおく
                _add_history     "sudo nvim $filename" $timestr
                # ファイルが読み取り専用なら sudo nvim として開く
                sudo nvim $filename
            else
                # これから実行するコマンドを fish_history に書き込んでおく
                _add_history     "nvim $filename" $timestr
                # ファイルが読み書き可能なら nvim として開く
                nvim $filename
            end
        end
        # 正常終了したので return 0 で関数を終える
        return 0
    # zf コマンド実行時に引数が指定されていた場合
    else if test (count $argv) -eq 1
        # 引数がファイルであれば、nvim で開くことを試みる
        if test -f $argv[1] 
            if test -r "$argv[1]"
                _add_history     "sudo nvim $argv[1]" $timestr
                sudo nvim $argv[1]
            else
                _add_history     "nvim $argv[1]" $timestr
                nvim $argv[1]
            end
        # 引数がディレクトリであれば、find でファイルを探索してから、
        # fzf によって対話形式でユーザにファイルを選択させる.
        else if test -d $argv[1] 
            set filename ( find $argv[1] -type f 2>/dev/null | fzf )
            if test -n "$filename"
                if test -r "$filename"
                    # これから実行するコマンドを fish_history に書き込んでおく
                    _add_history     "sudo nvim $filename" $timestr
                    # ファイルが読み取り専用なら sudo nvim として開く
                    sudo nvim $filename
                else
                    # これから実行するコマンドを fish_history に書き込んでおく
                    _add_history     "nvim $filename" $timestr
                    # ファイルが読み書き可能なら nvim として開く
                    nvim $filename
                end
            end
            # 正常終了したので return 0 で関数を終える
            return 0
        else
            echo "Error: could not found $argv[1]"
            return 1
        end
    else if test (count $argv) -gt 1
        # 引数が 2個以上指定された場合は、異常終了させる.
        echo "Usage: $argv[0] [directory]"
        return 1
    end
end

```

## 実行例

### 引数なしで実行する場合

カレントディレクトリ直下のファイルが候補として表示される

```bash
$ zf
```

### ファイルを指定する場合

nvim で開く

```bash
$ zf ~/.bashrc
```

### ディレクトリを指定する場合

指定したディレクトリ直下のファイルが候補として表示される

```bash
$ zf ~/.config/
```

### 引数を 2個以上指定した場合

エラー終了する. 下記のように終了コードは「1」となっている.

```bash
$ zf ~/.bashrc ~/.config/
Usage:  [directory]

$ echo $status
1 
```