# 令和時代のEmacsカラーテーマの作り方

Date: 2020-05-10
URL: https://s.conao3.com/blog/2020/91b392
Author: conao3 (Naoya Yamashita)

# Intro

昨日の夜あまりにも進捗が出ないので、寝てしまおうと思ったらこんな感じのツイートを不覚にも見てしまいました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">1000とかすごいですね! おめでとうございます!<br>これMITライセンスということはIcebergなEmacsのカラーtheme作ってもいいわけですね〜! やったー</p>&mdash; Nyoho (@NeXTSTEP2OSX) <a href="https://twitter.com/NeXTSTEP2OSX/status/1258757464582795264?ref&#95;src=twsrc%5Etfw">May 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ちょっと興味を引かれたので、さっとEmacsポートを作る気になりました。
今のSolarizedなら簡単にできるはずです。

「今の」というのは、私が出したパッチ「[[new feature] solarized.el as multi color scheme](https://github.com/bbatsov/solarized-emacs/pull/330)」が入ったSolarized。

いろいろ議論を大きくしてしまいましたが、結局私のコードは元気に動いており、私もいつも自作カラーパレットでSolarizedを使っています。

今回、iceberg-theme.elをどのように作ったのか、まとめておきます。

成果物はこちら。[iceberg-theme.el](https://github.com/conao3/iceberg-theme.el)

![img](https://s.conao3.com/img/91b392-e26870a4-d22e-4541-8340-522c3b5cae85.png)

# 前提知識

## Emacsにおけるface

`face` とは下記のように `defface` で各パッケージで定義されており、ユーザーのディスプレイ環境に応じて様々な色を設定できるようになっています。

下記の `face` の例において、 `feather` の例では `background` が `dark` か `light` かで色の分岐を行なっていますし、Emacs標準添付パッケージの `isearch` では `background` 判定に加え、最大表示色数も考慮されています。

```emacs-lisp
(defface feather-dashboard-header
  '((((background dark))
     :background "#223377"
     :foreground "white"
     :weight bold :height 1.3 :family "Sans Serif")
    (((background light))
     :background "#abd7f0"
     :foreground "black"
     :weight bold :height 1.3 :family "Sans Serif"))
  "Face for feather-dashboard header."
  :group 'feather)

(defface isearch-fail
  '((((class color) (min-colors 88) (background light))
     (:background "RosyBrown1"))
    (((class color) (min-colors 88) (background dark))
     (:background "red4"))
    (((class color) (min-colors 16))
     (:background "red"))
    (((class color) (min-colors 8))
     (:background "red"))
    (((class color grayscale))
     :foreground "grey")
    (t (:inverse-video t)))
  "Face for highlighting failed part in Isearch echo-area message."
  :version "23.1")
```

あとはこの `face` を言語をパース後、ある意味のところに貼り付けたり、オーバーレイである文字のところに適用したりして使います。

## Emacsにおけるカラーテーマ

Emacsのカラーテーマはこれらパッケージが提供する `face` を一括で変更してしまうものです。

具体例は[emacs-jp/replace-colorthemes](https://github.com/emacs-jp/replace-colorthemes)にたくさんあります。

このように `face` の名前と対応するスタイルを逐一変更するものがEmacsのカラーテーマです。

例えば[aalto-dark-theme](https://github.com/emacs-jp/replace-colorthemes/blob/master/aalto-dark-theme.el)の冒頭はこのようになっています。

```emacs-lisp
(deftheme aalto-dark
  "aalto-dark theme")

(custom-theme-set-faces
 'aalto-dark

 '(default ((t (:background "DeepSkyBlue3" :foreground "white"))))
 '(mouse ((t (:foreground "black"))))
 '(cursor ((t (:background "yellow"))))
 '(border ((t (:foreground "black"))))

 '(bold ((t (:bold t :background "blue3" :foreground "white"))))
 '(bold-italic ((t (:italic t :bold t :foreground "blue3"))))
 '(calendar-today-face ((t (:underline t))))
 '(diary-face ((t (:foreground "red"))))
 '(font-lock-builtin-face ((t (:foreground "LightSteelBlue"))))
 '(font-lock-comment-face ((t (:foreground "OrangeRed"))))
 ;; ...
 )

(provide-theme 'aalto-dark)
```

カラーテーマパッケージがどのようなことをしているか掴めてきましたでしょうか。

## カラーテーマの問題点

さて、Emacsの `face` は先程のようにパッケージごとに定義されています。
もし、パッケージが増えた場合、カラーテーマはどのように対応するべきでしょうか。

簡単ですね。新しいパッケージが増えたら、その都度 `face` の設定を書き足すのです。。

例えば[andreas-theme](https://github.com/emacs-jp/replace-colorthemes/blob/master/andreas-theme.el)では `gnus` や `paren` というパッケージに対応していることが分かります。

このようにカラーテーマごとに「対応しているパッケージ」が異なっており、メンテナンスされていないカラーテーマを使っていると、パッケージが決めたデフォルトの色がそのまま表示されることになります。

そのため「色が暗い」「色がビビッドすぎる」「そもそも見えない」などの問題が発生します。

# Solarized

## Solarizedのface定義

Emacsコミュニティによって長年メンテナンスされているSolarizedはどうなっているでしょうか。

実際のファイルは[solarized-emacs/solarized-faces.el](https://github.com/bbatsov/solarized-emacs/blob/master/solarized-faces.el)です。

`face` の再定義は2000行に及び、もし新しいパッケージが生まれたとしても有志の手によって速やかに追加されています。

これをSolarizedだけで使うのはもったいありません。

Solarizedも内部では基本となる10色を定義し、その色で `face` を再定義しています。

では、基本となる10色を外部から指定できるようにしたらSolarizedの仕組みに乗った上で簡単にカラーテーマが作れるのではないか。

それを提案したのがzk\_phiさんの「[[Emacs] メタ・カラースキームとしてのsolarized](https://qiita.com/zk_phi/items/35097fbb3dde4253d428)」であり、私のパッチはそれを本家で実現したものです。

## Solarizedから作るカラーテーマ

SolarizedのAPIで使いやすいのは `solarized-create-theme-file-with-palette` です。
この関数は最暗色、最明色に加え、アクセントカラー8色を指定することでSolarizedの `face` 定義を使うカラーテーマファイルを生成し、 `.emacs/themes` 以下に保存します。

早速icebergからアクセントカラーを貰ってカラーテーマを生成しましょう!

```emacs-lisp
(solarized-create-theme-file-with-palette 'dark 'solarized-iceberg-dark
  '("#161821" "#c6c8d1"
    "#e2a478" "#e27878" "#e27878" "#a093c7" "#b4be82" "#84a0c6" "#89b8c2" "#84a0c6"))
```

上記で書いた色はVim Scriptのソースからではなく[講演資料](https://speakerdeck.com/cocopon/creating-your-lovely-color-scheme?slide=51)から色を貰いました。

![img](https://s.conao3.com/img/91b392-e8eb5af7-a385-4d92-b404-11bb3d00633e.png)

なお、icebergではアクセントカラーが6色のようなので、Solarizedが求めているアクセントカラー8色に足りません。そのため2色をダブらせて生成しました。

関数を実行して `.emacs/themes` 以下に保存されたテーマファイルは `load-theme` でロードできます。

# 微調整の仕方

Emacsの本文部分は `default` という `face` が当てられているのですが、solarizedのデフォルトでは文字色が最明色になりません。。

まずそこからカスタマイズしていきます。

`solarized-create-theme-file-with-palette` の第3引数にS式のリストを渡すことでテーマのカスタマイズができます。

```emacs-lisp
(solarized-create-theme-file-with-palette 'dark 'solarized-iceberg-dark
  '("#161821" "#c6c8d1"
    "#e2a478" "#e27878" "#e27878" "#a093c7" "#b4be82" "#84a0c6" "#89b8c2" "#84a0c6")
  '((custom-theme-set-faces
     theme-name
     `(default ((,class (:foreground ,base3 :background ,base03)))))))
```

カスタマイズはSolarizedの内部で色がどのように管理されているかを少し知っておく必要があります。

基本的に次のように色の名前が管理されています。

    <brightest                                 darkest>
    base03 base02 base01 base00 base0 base1 base2 base3
    
    <accent colors>
    yellow orange red magenta violet blue cyan green

さらに色のバリエーションとして `yellow-d` や `yellow-l` という色も定義されており、第3引数で使用できます。

これらは `solarized-create-color-palette` という関数の中で自動生成されています。

# 完成形

本家のiceberg.vimのスクショと比較しながら、間違っている `face` を `describe-face` で確認して直す。。という作業を繰り返すと徐々に本家に近づいていきます。

最終的に次のような形になりました。

```emacs-lisp
(defun iceberg-theme-create-theme-file ()
  "Create iceberg-theme color theme using solarized API.

Solarized pallete.

brightest                                   darkest
base03 base02 base01 base00 base0 base1 base2 base3

yellow orange red magenta violet blue cyan green"
  (solarized-create-theme-file-with-palette 'dark 'solarized-iceberg-dark
    '("#161821" "#c6c8d1"
      "#e2a478" "#e27878" "#e27878" "#a093c7" "#b4be82" "#84a0c6" "#89b8c2" "#84a0c6")
    '((custom-theme-set-faces
       theme-name
       `(default ((,class (:foreground ,base3 :background ,base03))))
       `(vertical-border ((,class (:foreground ,base03))))
       `(mode-line ((,class (:foreground ,base2 :background ,base02))))
       `(mode-line-inactive ((,class (:foreground ,base0 :background ,base03))))

       `(font-lock-comment-delimiter-face ((,class (:foreground "#6b7089"))))
       `(font-lock-comment-face ((,class (:foreground "#6b7089"))))
       `(font-lock-preprocessor-face ((,class (:foreground ,green)))) ; yellow
       `(font-lock-type-face ((,class (:foreground ,cyan)))) ; yellow
       `(font-lock-builtin-face ((,class (:foreground ,green)))) ; base0

       `(diff-function ((,class (:foreground ,violet-1fg))))
       `(diff-header ((,class (:foreground ,green))))
       `(diff-hunk-header ((,class (:foreground ,green))))
       `(diff-file-header ((,class (:background ,base03 :foreground ,green))))
       `(diff-added ((,class (:background ,violet-1bg :foreground ,violet-1fg))))
       `(diff-indicator-added ((t (:foreground ,violet))))
       `(markdown-header-face ((,class (:foreground ,yellow))))
       `(markdown-header-rule-face ((,class (:foreground ,green))))
       `(markdown-markup-face ((,class (:inherit default))))
       `(markdown-url-face ((,class (:foreground ,magenta))))
       `(markdown-link-face ((,class (:foreground ,green :underline t))))
       `(markdown-inline-code-face ((,class (:foreground ,cyan))))
       `(markdown-pre-face ((,class (:foreground ,cyan))))
       `(sh-quoted-exec ((,class (:foreground ,violet))))
       `(haskell-type-face ((,class (:inherit default))))
       `(haskell-constructor-face ((,class (:inherit default))))
       `(haskell-operator-face ((,class (:foreground ,green))))
       `(haskell-definition-face ((,class (:inherit default))))
       `(web-mode-block-delimiter-face ((,class (:inherit default))))
       `(web-mode-html-attr-value-face ((,class (:foreground ,cyan))))
       `(web-mode-mode-type-face ((,class (:inherit default))))
       `(web-mode-function-call-face ((,class (:inherit default))))
       `(web-mode-keyword-face ((,class (:foreground ,green))))
       `(web-mode-constant-face ((,class (:foreground ,cyan))))
       `(web-mode-variable-name-face ((,class (:foreground ,cyan))))
       `(web-mode-html-tag-bracket-face ((,class (:foreground ,green))))
       `(org-verbatim ((,class (:foreground ,cyan))))
       `(php-php-tag ((,class (:inherit default))))
       `(php-constant ((,class (:inherit default))))
       `(php-paamayim-nekudotayim ((,class (:foreground ,green))))
       `(php-object-op ((,class (:foreground ,cyan))))
       `(php-variable-name ((,class (:foreground ,cyan))))
       `(php-variable-sigil ((,class (:foreground ,cyan))))))))
```

テーマを作る関数にしているのは「requireしただけでEmacsの動作を変えない」というパッケージ作成のルールに従うためです。

これでユーザーはiceberg-themeをインストール後、 `iceberg-theme-create-theme-file` を実行した後で `M-x load-theme solarized-iceberg-dark` と実行できるはずです。

# まとめ

現在ではSolarizedの資産を生かし、簡単にカラーテーマを作成することができます。

ぜひ独創的かつ目にやさしいカラーテーマを作っていただき、日々のEmacs生活がよりよいものになれば良いなと思っています。
