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

Intro

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

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

「今の」というのは、私が出したパッチ「[new feature] solarized.el as multi color scheme」が入ったSolarized。

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

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

成果物はこちら。iceberg-theme.el

前提知識

Emacsにおけるface

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

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

(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にたくさんあります。

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

例えばaalto-dark-themeの冒頭はこのようになっています。

(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では gnusparen というパッケージに対応していることが分かります。

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

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

Solarized

Solarizedのface定義

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

実際のファイルはsolarized-emacs/solarized-faces.elです。

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

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

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

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

それを提案したのがzk_phiさんの「[Emacs] メタ・カラースキームとしての solarized」であり、私のパッチはそれを本家で実現したものです。

Solarizedから作るカラーテーマ

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

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

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

上記で書いた色はVim Scriptのソースからではなく講演資料から色を貰いました。

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

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

微調整の仕方

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

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

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

(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-dyellow-l という色も定義されており、第3引数で使用できます。

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

完成形

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

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

(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生活がよりよりものになれば良いなと思っています。