Emacs Lisp で実装した日本語に対応した単純な GREP

# Emacs Lisp で実装した日本語に対応した単純な GREP
emacs lisp grep

# 日本語検索恐怖症

Windows だとまともに使える日本語対応の GREP が無くて、日本語が扱えてもオプション体系や表示結果が GREP とは違っていたりして、仲々馴染むものがありませんでした。

マッチ行のみ書き出す単純な GREP なら Emacs で簡単に書けるだろうけど、遅くて使いものにならないんじゃとか漠然と思っていたのですが、とりあえず書くだけ書いて試すことにしました。

外付け GREP のように BG で動かすことができず、結果が出るまで Emacs が操作できないので、実際に実用に耐えうるかどうかは環境や対象ファイル数にもよるのですが、ホームディレクトリからの全ディレクトリ再帰検索などでテストしてみると「待つかな?」と感じるのはファイルがキャッシングされていない PC 起動初期時だけで、平常時なら結構ストレスもなく利用できました。

何より Emacs 本体が行なうので日本語にも問題なく対応していて、サーチ指定する正規表現にも日本語を混ぜられるので普通に使えると思います。

# ダウンロード

[こちらから](https://gist.github.com/s-fubuki/8223e5f2a4c9c344755731f54501854d)

# 使い方

`bi-grep.el` を `load-path` の通ったディレクトリに置き、`init.el` で load して適宜キーアサインするなり直接起動するなりします。

```emacs-lisp:設定例
(require 'bi-grep)
(global-set-key "\C-cg" 'bi-grep)
```

起動すると最初に検索文字列(正規表現)を入力し、その次に対象ファイル(ファイルマッチ)を入力します。

検索文字のメタキャラクタは  `"\(foo\|bar\)"` のように Back Slash ひとつでエスケープします。

ファイル指定の方は `"text.(foo|bar)"` とエスケープなしで書きます。ややこしいですが、揃えると他で余計ややこしいので元の仕様そのままです。

ファイルマッチには eshell のグラブライブラリを使っているので、`grep -R` のようにしたいときはは `"**/*.txt"` 等と指定すると階層を降りていきます。

Directory 以下の特定種類のテキストから探したいときは `"**/*.(txt|org|el)"` 等と書きます。

```
C-h f eshell-glob-regexp 
```

とすると eshell のグラブで使えるパターンの一覧が見れます。

# grep -i (もしくは grep -y)

変数 `big-case-fold-search` が `非NIL` なら Case insensitive(大文字小文字を区別しない) で `NIL` なら Case sensitive(区別する) になります。

プレフィクス付で起動するとこの値を反転させて実行します。

つまりデフォルトなら大文字小文字を区別しませんが、プレフィクス付だと区別するようになり、この変数を nil にするとその逆になるということです。

# grep-mode について (Jul 29 2018)

bi-grep をカレントドライブで実行しなかったり、検索対象が別ドライブの場合、結果のファイル名に Drive Name が付きますが、その場合 bi-grep の結果を丸投げしている grep-mode がちゃんと動かないことが発覚したのでその応急対策です。
(Emacs 26.1 で気づきましたが前からそうだったのかは不明です。)

grep.el の中のカスタマイズ変数 `grep-regexp-alist` で設定している list のほんの一部だけ変更すればいいで `delete` 等でそこだけ抜いて差し替えたいのですが、変数設定が式になっていてその評価結果が結構長く、そこだけ変更しても結局長くなるので、すっきり丸ごと書き換えます(setq で丸々上書きします)。

まだ十分なテストをしていないので他に影響があるかどうかは今のところ不明です。
自己責任において注意して利用お願いします。

init.el 等に以下を貼り付けてください。

```emacs-lisp:設定例
(setq grep-regexp-alist
  `((,(concat "^\\(?:"
              ;; Parse using NUL characters when `--null' is used.
              ;; Note that we must still assume no newlines in
              ;; filenames due to "foo: Is a directory." type
              ;; messages.
              "\\(?1:[^\0\n]+\\)\\(?3:\0\\)\\(?2:[0-9]+\\):"
              "\\|"
              ;; Fallback if `--null' is not used, use a tight regexp
              ;; to handle weird file names (with colons in them) as
              ;; well as possible.  E.g., use [1-9][0-9]* rather than
              ;; [0-9]+ so as to accept ":034:" in file names.
              "\\(?1:[^\n]+?[^\n/]\\):[\t ]*\\(?2:[1-9][0-9]*\\)[\t ]*:"
              "\\)")
     1 2
     ;; Calculate column positions (col . end-col) of first grep match on a line
     (,(lambda ()
         (when grep-highlight-matches
           (let* ((beg (match-end 0))
                  (end (save-excursion (goto-char beg) (line-end-position)))
                  (mbeg (text-property-any beg end 'font-lock-face 'grep-match-face)))
             (when mbeg
               (- mbeg beg)))))
      .
      ,(lambda ()
         (when grep-highlight-matches
           (let* ((beg (match-end 0))
                  (end (save-excursion (goto-char beg) (line-end-position)))
                  (mbeg (text-property-any beg end 'font-lock-face 'grep-match-face))
                  (mend (and mbeg (next-single-property-change mbeg 'font-lock-face nil end))))
             (when mend
               (- mend beg))))))
     nil nil
     (3 '(face nil display ":")))
    ("^Binary file \\(.+\\) matches$" 1 nil nil 0 1)))
```    

結果的にやっていることは
この行から

```
"\\(?1:[^\n:]+?[^\n/:]\\):[\t ]*\\(?2:[1-9][0-9]*\\)[\t ]*:"
```

以下のようにキャラクタクラスの中のコロンを抜くだけです。

```
"\\(?1:[^\n]+?[^\n/]\\):[\t ]*\\(?2:[1-9][0-9]*\\)[\t ]*:"
```

# 小さな変更 (Sat Oct 27 10:32:45 2018)

ミニバッファから正規表現を受け取る関数を `read-string` から `read-regexp` に変更しました。
それにより `M-n` 等のヒストリでカーソル位置の文字列が拾えたりします。
動的補完で済ませていたのでこの関数のことは知りませんでした。

# 動作確認している Emacs のバージョン

> GNU Emacs 26.1 (build 1, i686-w64-mingw32) of 2018-05-31

ちなみに GNU のサイトにあるものです。