Emacs のモードラインを複数切り替えて使う

Emacs では様々な情報をモードラインに表示しておくことができます。デフォルトのままだと少し使いにくいですが、カスタマイズして自分の欲しい情報を表示したり、わかりやすい表記に変えたりしておくとたいへんべんりです。ゼロから自分で Elisp を書くのもさほど難しくはありませんが、私は `doom-modeline.el` をベースに自分好みに設定しています。

ここで悩ましいのが、モードラインの幅は限られているということです。フレームを分割することも考えると、あまり多くの情報は表示できません。そこで、モードラインを2種類用意し、片方に常に表示させておきたい情報を、もう片方にたまに見たくなる情報を表示するように設定して、適宜切り替えて使う、という運用をしています。

# doom-modeline のカスタマイズ
doom-modeline については知っていて、モードラインを切り替える方法だけが知りたい人は、この節は飛ばしても問題ありません。

`doom-modeline.el` を使うと、`doom-modeline-def-segment` というマクロでモードラインに表示させたい内容をセグメントとして定義し、それらセグメントを組み合わせて `doom-modeline-def-modeline` という関数でモードラインを定義することができます。たいへんらくちんです。標準的に必要そうなセグメントはあらかじめ定義されているため、それだけでこと足りる場合もありますが、表示のされ方が不満だったり、欲しいセグメントが無い場合には自分で定義する必要があります。

## セグメントの定義
最もシンプルな例として、バッファサイズを表示するセグメントは以下のように定義できます:

```elisp
(doom-modeline-def-segment buffer-size
  "Display current buffer size"
  (format-mode-line " %IB"))
```

`format-mode-line` を使うことで、お手軽にいろいろな情報をモードラインに載せることができます。取れる%引数は [Emacsのマニュアル](https://www.gnu.org/software/emacs/manual/html_node/elisp/_0025_002dConstructs.html) に掲載されています。ここでは `%I` でバッファサイズの文字列を表示し、そのあとに「バイト」の意味で文字列の `B` をくっつけてる、といった感じです。

モードラインに表示するセグメント全てを空白文字で始めるようにしておくと、複数のセグメントを組み合わせた際にいい感じに区切って表示されるようになります。

もう少し複雑な例として、行番号と列番号を表示する例を示しておきます：

```elisp
(doom-modeline-def-segment linum-colnum
  "Display current linum/colnum"
  (propertize (format " Ln %s/%s, Col %s"
                      (format-mode-line "%l")
                      (line-number-at-pos (point-max))
                      (format-mode-line "%c"))
              'face '(:foreground "#8cd0d3" :weight bold)))
```

`propertize` 関数を使うことで、テキストのプロパティを様々に設定できます。ここでは文字色の指定とボールド表示の指定を行っています。他にもマウスでクリックした際の挙動なども設定しておくことができます。詳しくは [Text Propertizeのマニュアル](https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Properties.html) を見てみましょう。

## モードラインの定義
さて表示したい内容を全てセグメントとして用意したら、あとはモードラインを定義するだけです。これは非常に簡単で、以下のように書けます：

```elisp
(doom-modeline-def-modeline 'my-modeline
  '(bar evil-state matches remote-host buffer-info pdf-pages linum-colnum)
  '(projectile-project-name python-venv vcs checker fancy-battery datetime))
```

(注: いくつかのセグメントは私が定義したものなので、デフォルトでは用意されていません)

`doom-modeline-def-modeline` 関数に、モードライン名、左端に寄せて表示したいセグメント名のリスト、右端に寄せて表示したいセグメント名のリスト、の3つを渡してあげるだけです。

あとは、定義したモードラインを使うように設定すれば完了です。

```elisp
(defun setup-initial-doom-modeline ()
  (doom-modeline-set-modeline 'my-modeline t))
(add-hook 'doom-modeline-mode-hook #'setup-initial-doom-modeline)
```

# 複数モードラインの切り替え
モードラインはいくつでも定義しておくことができるので、メインとサブの2つのモードラインを定義しておき、それをスイッチして使えるようにします。ここでは、`main-modeline` と `sub-modeline` の2つが定義してあるものとします。

```elisp
(defvar doom-modeline-main-p t)
(defun switch-modeline ()
  (interactive)
  (if doom-modeline-main-p
      (doom-modeline-set-modeline 'sub-modeline)
    (doom-modeline-set-modeline 'main-modeline))
  (force-mode-line-update)
  (setq doom-modeline-main-p (not doom-modeline-main-p)))
(bind-key "C-l C-m" 'switch-modeline)
```

(注: 私は自分用のプレフィクスとして `C-l` を使うようにしています。デフォルトだと `C-l` は `recenter-top-bottom` にバインドされているので、その場合は適宜他のキーバインドを選んでください)

このようにしておくと、`C-l C-m` を連打することで2つのモードラインを行ったり来たりすることができます。3つ以上のモードラインを切り替えたいなら、サイクルするようにしたり、直接選択するようにしたりできるでしょう。

# 設定全体
最後に私の doom-modeline の設定全体を載せておきます。

```elisp
(use-package doom-modeline
  :ensure t
  :hook
  (after-init . doom-modeline-mode)
  :commands
  (doom-modeline-def-modeline doom-modeline-def-segment)
  :init
  (defun remove-padding-zero (num)
    (if (string= (substring num 0 1) "0")
        (substring num 1)
      num))

  (doom-modeline-def-segment buffer-size
    "Display current buffer size"
    (format-mode-line " %IB"))

  (doom-modeline-def-segment projectile-project-name
    "Display Projectile project name"
    (if (and (boundp 'projectile-mode) projectile-mode)
        (propertize (format " Project: %s"
                            (projectile-default-project-name (projectile-project-root)))
                    'face '(:foreground "#8cd0d3" :weight bold))
      ""))

  (doom-modeline-def-segment linum-colnum
    "Display current linum/colnum"
    (propertize (format " Ln %s/%s, Col %s"
                        (format-mode-line "%l")
                        (line-number-at-pos (point-max))
                        (format-mode-line "%c"))
                'face '(:foreground "#8cd0d3" :weight bold)))

  (doom-modeline-def-segment datetime
    "Display datetime on modeline"
    (let* ((system-time-locale "C")
           (dow (format "%s" (format-time-string "%a")))
           (month (format "%s" (remove-padding-zero (format-time-string "%m")) ))
           (day (format "%s" (remove-padding-zero (format-time-string "%d"))))
           (hour (format "%s" (remove-padding-zero (format-time-string "%I"))))
           (minute (format-time-string "%M"))
           (am-pm (format-time-string "%p")))
      (propertize
       (concat
        " "
        hour
        ":"
        minute
        am-pm
        "  "
        )
       'help-echo "Show calendar"
       'mouse-face '(:box 1)
       'local-map (make-mode-line-mouse-map
                   'mouse-1 (lambda () (interactive) (calendar))))))

  (doom-modeline-def-segment python-venv
    "Display current python venv name"
    (if (eq major-mode 'python-mode)
        (let ((venv-name (if (or (not (boundp 'pyvenv-virtual-env-name))
                                 (eq pyvenv-virtual-env-name nil))
                             "GLOBAL"
                           pyvenv-virtual-env-name)))
          (propertize (format " [%s]" venv-name) 'face '(:foreground "#f0dfaf" :weight bold)))
      ""))

  (with-eval-after-load 'evil
    (doom-modeline-def-segment evil-state-seg
      "Display current Evil State."
      (propertize (format " <%s>" (upcase (substring (symbol-name evil-state) 0 1)))
                  'face '(:weight bold)))
    (doom-modeline-def-modeline 'simple
      '(bar evil-state-seg matches remote-host buffer-info  pdf-pages linum-colnum)
      '(projectile-project-name python-venv vcs checker fancy-battery datetime)))

  (doom-modeline-def-modeline 'verbose
    '(bar matches remote-host buffer-info-simple buffer-size)
    '(major-mode minor-modes buffer-encoding))

  (defun setup-initial-doom-modeline ()
    (doom-modeline-set-modeline 'simple t))
  (add-hook 'doom-modeline-mode-hook #'setup-initial-doom-modeline)

  (defvar doom-modeline-simple-p t)
  (defun switch-modeline ()
    (interactive)
    (if doom-modeline-simple-p
        (doom-modeline-set-modeline 'verbose)
      (doom-modeline-set-modeline 'simple))
    (force-mode-line-update)
    (setq doom-modeline-simple-p (not doom-modeline-simple-p)))
  (bind-key "C-l C-m" 'switch-modeline)
  :config
  (setq doom-modeline-minor-modes t)
  (setq doom-modeline-major-mode-color-icon t)
  (setq doom-modeline-checker-simple-format nil))
```