Emacs で文字数をカウントしたいとき(または私用のキーバインドとメニュー設定)

<!-- ;; Sat Oct  3 07:26:04 2020 -->
- [[デフォルトバインドされていることに気づきました(Sat Oct  3 07:34:23 2020)](#count-words-region)]
- [メニューに関する箇所を更新しました(Sat Sep 12 08:06:58 2020)]

# バッファ文字数のカウント
web フォームで文字数が決まっているものがときどきありますが、 たまにしかない場面なので Emacs 上でのカウントのしかたが身に着いていなくて、 その都度ググったり info をひっくり返したりしていました。 でも毎回やっていたんじゃ時間や手間が結構馬鹿になりません。

結局 `count-words` というのを使えばカレントバッファの文字数、 もしくはリージョンがあればリージョンの文字数が判るんですが、 キーバインドだけしておいてもまたすぐ忘れるので、メニューに登録しておくことによってすぐ判るようにしました。

```lisp:init.el
(define-key-after global-map [menu-bar tools cwords]
  '("Count Words" . count-words)
  'spell)
```
これで `Menu` ->  `Tools` の中の `Spell Checking` の下に件のコマンドが登録されます。

文字数カウントについては以上です。
以下はキーやメニュー(初稿からアップデートされています)の拡張の仕方なので興味のある方だけどうぞ。

# 私用キーマップ
ついでにキーにバインドするなら、マイマップを用意しておくと追加が便利になります。

Emacs の掟で `C-c` と`アルファベット`[^1]からなるシーケンスはユーザー用に予約解放済みなのでそれを例に説明します。

[^1]: 普通の大文字もしくは小文字のアルファベットのいずれかで`コントロール+アルファベット`はアルファベットではなくコントロールコードなので含まれない。ちなみに自分は私的バインド空間を `C-cM` にしていて、これなら衝突を気にせずコントロール・コードでも記号でも自由にバインドできるようになります。

```lisp:下準備(init.el等)
(define-prefix-command 'priv-map)
(define-key global-map "\C-c" 'priv-map)
```

<!--  > `define-prefix-command` は `defvar` と `make-*-keymap` `fset` を組み合わせても同じことができます -->

`priv-map` の箇所はお好きな名前で、上記のようにして個人用プレフィクス・マップを作ってバインドしておくと

```lisp:init.el
(define-key global-map "\C-cw" 'count-words)
```

などと毎回頭からフルストロークで指定しないでも

```lisp:init.el
(define-key priv-map "w" 'count-words)
```

などと 1キャラ指定で個人用バインドをシンプルに追加していけて整理管理も楽になります。

ちなみに自分は whch-key-mode を使っていて、バインドを忘れていてもこれがヘルプになるのですが、 入れていないデフォルト状態でもプレフィクスの後に `C-h` を押すとバインドの一覧が出るので覚えていると素の Emacs を使う場面等で役に立ちます。

# 私用メニューマップ
メニューは `easy-menu-add-item` を使うと設定も追加もこれだけでできます。標準添付関数ですが `init.el` で使うにはロードが必要かもしれません。

```lisp:init.el
(require 'easymenu)
(easy-menu-add-item
 global-map
 '(menu-bar)
 '("Private" ; これがそのままマップのシンボルになる.
   ["Count Words" count-words]
   ["imenu"       imenu-add-menubar-index]
   ["Remember"    remember]
   ["Dup Line"    dup-line]
   ["Follow Win"  follow-delete-other-windows-and-split]
   ("TimeClock"  
    ["In"  timeclock-in]
    ["Out" timeclock-out]
    ["Change" timeclock-change]
    ["Log" timeclock-visit-timelog])))
```

こんな感じで初期化なしでいきなりまとめて設定でき、入れ子にすれば階層構造の表現もできるようです。
グローバル・マップとメジャー・モード等の並びの間の追加メニューとしては理想的な位置に出現します。

メニュー名がそのままキーマップの名前(ダミーファンクションキーの名前)になるようなので、
このメニューに後からコマンドを追加したい場合は以下の要領で書くだけでできます。

```lisp:
(easy-menu-add-item global-map '(menu-bar Private) ["テスト"  version])
```

オプションの4番目の引数をつけると位置の指定もでき、そのメニュー項目の上に挿入されます。何も指定しないと最後になります。

```lisp:
(easy-menu-add-item global-map '(menu-bar Private) ["テスト2" version] "TimeClock")
```

最初の count-words メニュー追加案件は(位置は変わりますが)以下一発でも OK だということです。

```lisp:
(easy-menu-add-item global-map '(menu-bar Tools) '["Count Word" count-words] "Spell Cheking")
```

# メニューへのショートカット
メニューのキーボード・ショートカットがないか調べてみると `menu-bar-open` と `tmm-menubar (Meta + BackQuart)` の2種をとりあえず発見。

前者はフルキーからいけないぽいので使いやすそうなのは後者。
それに後者はデフォルト・バインドもされていて時々誤操作で開いて何だか判らずそっ閉じしていたもの。

これをしばらく使ってみようと思いましたが、デフォルトだと視認性がイマイチなので `which-key` に似せた色付けをしてみました。 ちなみに頭の長い英語のヘルプメッセは変数 `tmm-completion-prompt` で変更できるようです。

```lisp:init.el
(add-hook 'completion-list-mode-hook
          '(lambda ()
             (let ((prompt (if (boundp 'tmm-mid-prompt) tmm-mid-prompt "xxx")))
               (font-lock-add-keywords
                nil
                `((" ---[-0-9A-Za-z(/*+,#)]+\\([ ]?[-0-9A-Za-z(/*+,#)]+\\)*" . 'dired-ignored)
                  (,(concat
                     "\\(?1:[0-9A-Za-z%]\\)\\(?2:" prompt
                     "\\)\\(?3:[-0-9A-Za-z(</*+,.#>)]+\\(?:[ ]?[-0-9A-Za-z(</*+,.#>)]+\\)*\\)")
                   (1 font-lock-constant-face)
                   (2 font-lock-doc-face)
                   (3 font-lock-function-name-face)))))))
```

![tmmbar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239081/451fe426-ac4b-d03d-6d00-9efe9b4f071c.png)

dired 等多機能なモードのとき起動すると何気にいいヘルプになりますね。

## 「私的メニュー追加」に間する初稿
[この項 は`easy-menu-add-item` の項の追記で要らなくなった先に書いてあったメニュー追加記事です。 が、こちらも知っておくとメニュー設定の仕組が見えてきます。]

更なるついでにメニューの追加法です。普段そんなに使わないものや自作マクロ等なら私用メニューを作って詰めこんでおくといいでしょう。これも少々指定が増えますがほぼ同じ要領で追加できます。

```lisp:下準備(init.el等)
(define-prefix-command 'priv-menu-map nil "Private")
(define-key-after global-map [menu-bar private] '("Private" . priv-menu-map))
```

ブラケットの引数がダミーのファンクションキーで、 (private の箇所はこの名前でなくてもいいのですが判りやすいようにします) そのキーに `priv-menu-map` を割り振り `Private` というメニュー・ラベルを貼り付けます。ラベルは勿論 `日本語` でも大丈夫です。

`define-key` ではなく `define-key-after` ですが、こちらにしないとメニュー先頭(左端)に追加されてしまいます。[^2][^3]

[^2]: `easy-menu-define` とかいうどこが easy なんだか判らないマクロがありますが、これで global-map に追加すると先頭に追加されてしまい回避法が判りませんでした。[^3]

[^3]: `easy-menu-add-item` を使えば回避できることが判りました。

あとは以下のように随時 `priv-menu-map` に割り当てれば メニュー Private の中にコマンドを追加していけます。

```lisp:init.el
(define-key priv-menu-map [imenu]   '("Imenu Index"    . imenu-add-menubar-index))
(define-key priv-menu-map [dupline] '("Duplicate Line" . dup-line))
```

# オマケ
拙作 の Mew 用の拡張を Mew のメニューに追加 [Menu -> Mew -> Select の中]

```lisp:.mew.el
(define-key-after ;; "1日以内のメール" 
  (lookup-key mew-summary-mode-map [menu-bar Mew Select])
  [day]
  '("Within 1 Day" . mew-indays))
(define-key-after ;; "全文検索"
  (lookup-key mew-summary-mode-map [menu-bar Mew Select])
  [pic]
  '("Full Text Search" . mew-summary-selection-by-search))
```

# 関連リンク
- [mew-indays](https://qiita.com/s-fubuki/items/0b64540f4bf551c671b4)
- [mew-search-with-builtin for BG](https://qiita.com/s-fubuki/items/10102033e4d67a2d7772)


<!-- Sun Sep 27 08:10:28 2020 -->
# count-words-region

他のことを調べていて`count-words-region` として `M-=` に標準バインドされていた事に気づきました。
*scratch* で `C-h b` してバインド一覧をとって `count` で occur してみると見事にマッチ。
記事を書く前に確認したと思ったんですがそう思い込んでいたようです。お恥かしや。

```lisp:
7 matches for "count" in buffer: *Help*
    312:<f3>            kmacro-start-macro-or-insert-counter
    493:C-x l           count-lines-page
    557:M-=             count-words-region
    841:C-c M =         count-words
    870:C-x C-k C-a     kmacro-add-counter
    871:C-x C-k C-c     kmacro-set-counter
    875:C-x C-k TAB     kmacro-insert-counter
```

設定も掘ってみると見事にデフォルトバインドされています。

```lisp:bindings.el
3 matches for "count" in buffer: bindings.el
    753:	buffer-display-count buffer-display-time
    911:(define-key esc-map "=" 'count-words-region)
   1360:(define-key ctl-x-map "l" 'count-lines-page)
```

デフォルト・バインドされていれば、基本的に Emacs 流の伝統的規則性があって覚えられるので、 メニューに当てておくこともないんですが、 menu にもマッピングされているのか見てみましたが、こちらにはセットされていませんでした。

```lisp:menu-bar.el
4 matches in 3 lines for "count" in buffer: menu-bar.el
   2078:	(count 0))
   2081:	  (setq count (1+ count)))
   2083:    (> count 1)))
```