モードラインの文字エンコーディング表示をわかりやすくする

# 文字エンコーディング表示

ファイルを編集することを主目的とするエディタやIDEには、編集中のファイルの文字エンコーディング（および改行文字）が何であるかを表示する機能がついているものです。例えばIntelliJ IDEAの下部に以下のような表示があり、ファイルの文字エンコーディングを表現しています。

![intellij.png](https://qiita-image-store.s3.amazonaws.com/0/11126/f5367ec8-bcba-9754-95f5-99f265c1983e.png)

一方のEmacsではモードラインにその情報を表示しています。

![utf-8-dos.png](https://qiita-image-store.s3.amazonaws.com/0/11126/3e2b7653-82b0-dae6-a6ee-303189626389.png)

え、どこ？？？ とみんな思ったでしょう。実は

![koko.png](https://qiita-image-store.s3.amazonaws.com/0/11126/12b0ec41-a9b0-3ce0-fa1a-1439ba11df0a.png "ここ")

なんです。`U` は `UTF-8` を表しており、`\` は改行文字が `CRLF` であることを表しています。自分はEmacsを長く使ってきたので慣れてしまいましたが、初めて見る人は「そんなのわかんねーよ」と叫びEmacsから遠ざかるでしょう。

こういったぱっと見のわかりにくさの他にも、文字エンコーディングが `UTF-8`、`UTF-16` の両方ともモードラインでは `U` と表示されてしまうため、バッファを見ただけでは `UTF-8` と `UTF-16` の区別が付かないという問題もあります。また仕事でたまにBOM付きのファイルを触ることがあるのですが、デフォルトのモードラインではBOMの有無がわかりません。

そこで、この記事ではモードラインの文字エンコーディング表現を変更し、文字エンコーディング、BOM、改行文字をわかりやすくする方法を紹介します。

# 設定方法

まず結論として、以下のコードを `init.el` に加えれば文字エンコーディングの表示が変わります。以下では設定の中身を解説します。

```elisp:init.el
(require 'cl-lib)

;; 改行文字の文字列表現
(set 'eol-mnemonic-dos "(CRLF)")
(set 'eol-mnemonic-unix "(LF)")
(set 'eol-mnemonic-mac "(CR)")
(set 'eol-mnemonic-undecided "(?)")

;; 文字エンコーディングの文字列表現
(defun my-coding-system-name-mnemonic (coding-system)
  (let* ((base (coding-system-base coding-system))
         (name (symbol-name base)))
    (cond ((string-prefix-p "utf-8" name) "U8")
          ((string-prefix-p "utf-16" name) "U16")
          ((string-prefix-p "utf-7" name) "U7")
          ((string-prefix-p "japanese-shift-jis" name) "SJIS")
          ((string-match "cp\\([0-9]+\\)" name) (match-string 1 name))
          ((string-match "japanese-iso-8bit" name) "EUC")
          (t "???")
          )))

(defun my-coding-system-bom-mnemonic (coding-system)
  (let ((name (symbol-name coding-system)))
    (cond ((string-match "be-with-signature" name) "[BE]")
          ((string-match "le-with-signature" name) "[LE]")
          ((string-match "-with-signature" name) "[BOM]")
          (t ""))))

(defun my-buffer-coding-system-mnemonic ()
  "Return a mnemonic for `buffer-file-coding-system'."
  (let* ((code buffer-file-coding-system)
         (name (my-coding-system-name-mnemonic code))
         (bom (my-coding-system-bom-mnemonic code)))
    (format "%s%s" name bom)))

;; `mode-line-mule-info' の文字エンコーディングの文字列表現を差し替える
(setq-default mode-line-mule-info
              (cl-substitute '(:eval (my-buffer-coding-system-mnemonic))
                             "%z" mode-line-mule-info :test 'equal))
```

## 改行文字の文字列表現

3～7行目は改行文字に対する表示を変更しています。`eol-mnemonic-*` という変数が改行文字ごとに用意されているので、その設定値を変更しているだけです。

## 文字エンコーディングの文字列表現

9～34行目は文字エンコーディングに対して、独自の文字列表現を返す関数を定義しています。`my-coding-system-name-mnemonic` は文字エンコーディング自体の文字列表現、`my-coding-system-bom-mnemonic` がBOMの有る無しによる文字列表現を返します。

`my-buffer-coding-system-mnemonic` は上記2つを結合して返すだけの関数です。

## `mode-line-mule-info` の文字エンコーディングの文字列表現を差し替える

36～40行目で文字エンコーディングの表示を、デフォルトのものから独自に定義した関数を使用するように差し替えています。

デフォルトの文字エンコーディングの表示設定は `mode-line-mule-info` という変数が担っています。`mode-line-mule-info` は以下のような4要素のリストです。

```elisp
("" <インプットメソッドの文字列表現> <文字エンコーディングの文字列表現> <改行文字の文字列表現>)
```

3要素目の `<文字エンコーディングの文字列表現>` を独自のものに差し替えているのが、36～40行目のコードです。`"%z"` や `:eval` の意味は変数 `mode-line-format` のドキュメントを読んでください。

# 比較

以下に変更前後でモードラインがどのように変わるかを示します。

| 文字エンコーディング | BOM  | 改行文字 | 変更前                                   | 変更後                                  |
|----------------------|------|----------|------------------------------------------|-----------------------------------------|
| UTF-8                | なし | CRLF     | ![][before:utf-8-dos]                    | ![][after:utf-8-dos]                    |
| UTF-8                | なし | CR       | ![][before:utf-8-mac]                    | ![][after:utf-8-mac]                    |
| UTF-8                | なし | LF       | ![][before:utf-8-unix]                   | ![][after:utf-8-unix]                   |
| UTF-8                | あり | LF       | ![][before:utf-8-with-signature-unix]    | ![][after:utf-8-with-signature-unix]    |
| UTF-16               | BE   | LF       | ![][before:utf-16be-with-signature-unix] | ![][after:utf-16be-with-signature-unix] |
| UTF-16               | LE   | LF       | ![][before:utf-16le-with-signature-unix] | ![][after:utf-16le-with-signature-unix] |
| Shift JIS            | -    | LF       | ![][before:japanese-shift-jis-unix]      | ![][after:japanese-shift-jis-unix]      |
| CP932                | -    | LF       | ![][before:japanese-cp932-unix]          | ![][after:japanese-cp932-unix]          |
| EUC JP               | -    | LF       | ![][before:iso-japanese-8bit-unix]       | ![][after:iso-japanese-8bit-unix]       |

[before:iso-japanese-8bit-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/0f066332-8566-6770-7f1e-d283785916c7.png
[before:japanese-cp932-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/dc309626-bdba-7097-65ec-9005bfd52a6b.png
[before:japanese-shift-jis-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/4193b2fb-7f61-0e30-5aa4-a2e447a52ece.png
[before:utf-8-dos]: https://qiita-image-store.s3.amazonaws.com/0/11126/87d369e9-be4e-77d9-c472-a850eab70ca9.png
[before:utf-8-mac]: https://qiita-image-store.s3.amazonaws.com/0/11126/d1673ee5-f975-d5c6-961b-693df49eb6bf.png
[before:utf-8-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/d7b957c4-e553-d52a-4829-fae4add79591.png
[before:utf-8-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/6dd26058-5f55-ba1a-dcbc-49785ca209c2.png
[before:utf-16be-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/cf109fee-3cba-fc3a-430e-19f14ef33b7f.png
[before:utf-16le-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/6f655aa8-f469-7a8a-4661-00cbcc4f26e7.png

[after:iso-japanese-8bit-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/644274a1-26e0-4d71-1ace-dcf4e0b77fdf.png
[after:japanese-cp932-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/51e52741-4773-7012-d265-9a52baf555bc.png
[after:japanese-shift-jis-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/82f66856-ae75-01e8-6fe8-f04d39890a99.png
[after:utf-8-dos]: https://qiita-image-store.s3.amazonaws.com/0/11126/d0f6ad3c-0288-0ad1-693a-56c8e054d70e.png
[after:utf-8-mac]: https://qiita-image-store.s3.amazonaws.com/0/11126/a4ef40ed-a6b3-a3ed-2d0b-2a8cda03f563.png
[after:utf-8-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/55729d65-471d-5166-8988-4ec23ba5cd62.png
[after:utf-8-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/35591d44-cfe4-63a4-716a-06ea29c4ca8d.png
[after:utf-16be-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/908e930b-36c2-9a94-9725-fcd02c5d64d5.png
[after:utf-16le-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/9fcec1f4-f9ae-9387-5ffc-58a76fd311ca.png

# デフォルトとの機能差

デフォルト設定ではモードラインの文字エンコーディング部分をクリックすると、文字エンコーディングの説明を見たり、文字エンコーディングを変更する機能があります。しかしコードが長くなりすぎるのも嫌だったので、上記の設定ではそれを再現していません。

# 検証環境

WindowsのEmacs25.3でしか動作確認していないので、他の環境だと問題が出るかもしれません。

```
GNU Emacs 25.3.1 (i686-w64-mingw32)
 of 2017-09-16
```

その場合はコメントなどでご指摘ください。

# 参考

- https://www.reddit.com/r/emacs/comments/1nihkt/how_to_display_full_charset_name_in_modeline_eg/ccjmfdz/
    - コードの大筋はここを参考にしています。
- https://www.gnu.org/software/emacs/manual/html_node/elisp/_0025_002dConstructs.html#g_t_0025_002dConstructs
    - `"%z"` の説明。
- https://www.gnu.org/software/emacs/manual/html_node/elisp/Mode-Line-Data.html#Mode-Line-Data
    - `:eval` の説明。

<!--

- [1.3 The Mode Line](https://www.gnu.org/software/emacs/manual/html_node/emacs/Mode-Line.html#Mode-Line)
- [22.6 Recognizing Coding Systems](https://www.gnu.org/software/emacs/manual/html_node/emacs/Recognize-Coding.html)


↓大きいサイズの画像

[bck:before:iso-japanese-8bit-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/bab83548-67aa-487b-990c-73b3ccd5a67d.png
[bck:before:japanese-cp932-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/088f3645-7bb1-6328-ff19-0d001590f936.png
[bck:before:japanese-shift-jis-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/7dbfeb10-68dd-3134-9b4d-1c8b3396c84a.png
[bck:before:utf-16be-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/c8d4b601-d2d6-1a25-cc03-7143d1cfbbf6.png
[bck:before:utf-16le-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/5d653272-3d2f-5db4-70ab-f7a89ee3d53b.png
[bck:before:utf-8-dos]: https://qiita-image-store.s3.amazonaws.com/0/11126/a0a7e1a9-49a0-49bd-cd6c-0f63daeb4db6.png
[bck:before:utf-8-mac]: https://qiita-image-store.s3.amazonaws.com/0/11126/3c5076f2-7b59-8725-4893-7efa336432de.png
[bck:before:utf-8-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/50ee9a58-55be-e3ea-77c4-f27a681ad92b.png
[bck:before:utf-8-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/f06e7854-c815-db3a-fd56-2a6c9b5ee999.png

[bck:after:iso-japanese-8bit-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/2a96a33d-d992-f2b3-47cd-dea6156c748d.png
[bck:after:japanese-cp932-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/741ec93a-8b91-d179-bacd-84ca7cf1f242.png
[bck:after:japanese-shift-jis-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/4183922e-8b42-6fe7-b78e-efa60c89af58.png
[bck:after:utf-16be-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/51799d92-2aec-7729-bfe7-865d7ecc4d4a.png
[bck:after:utf-16le-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/b58b4038-0eda-d667-47ef-06b233e9281d.png
[bck:after:utf-8-dos]: https://qiita-image-store.s3.amazonaws.com/0/11126/4803d766-1ba9-f7d5-a182-9ee88aaecd20.png
[bck:after:utf-8-mac]: https://qiita-image-store.s3.amazonaws.com/0/11126/6dcceeb7-dc7d-e075-e8e0-508f17ec1a0b.png
[bck:after:utf-8-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/6411285a-da2c-0cef-c307-b03d808df986.png
[bck:after:utf-8-with-signature-unix]: https://qiita-image-store.s3.amazonaws.com/0/11126/395fd8a9-f268-c056-8abb-ec469dabb20b.png

 文字エンコーディング in Emacs  |
--------------------------------|
 `utf-8-dos`                    |
 `utf-8-mac`                    |
 `utf-8-unix`                   |
 `utf-8-with-signature-unix`    |
 `utf-16be-with-signature-unix` |
 `utf-16le-with-signature-unix` |
 `japanese-shift-jis-unix`      |
 `japanese-cp932-unix`          |
 `iso-japanese-8bit-unix`       |

-->