Intro
昨日の夜あまりにも進捗が出ないので、寝てしまおうと思ったらこんな感じのツイートを不覚にも見てしまいました。
1000とかすごいですね! おめでとうございます!
— Nyoho (@NeXTSTEP2OSX) May 8, 2020
これMITライセンスということはIcebergなEmacsのカラーtheme作ってもいいわけですね〜! やったー
ちょっと興味を引かれたので、さっとEmacsポートを作る気になりました。 今のSolarizedなら簡単にできるはずです。
「今の」というのは、私が出したパッチ「[new feature] solarized.el as multi color scheme」が入ったSolarized。
いろいろ議論を大きくしてしまいましたが、結局私のコードは元気に動いており、私もいつも自作カラーパレットでSolarizedを使っています。
今回、iceberg-theme.elをどのように作ったのか、まとめておきます。
成果物はこちら。iceberg-theme.el
前提知識
Emacsにおけるface
face
とは下記のように defface
で各パッケージで定義されており、ユーザーのディスプレイ環境に応じて様々な色を設定できるようになっています。
下記の face
の例において、 feather
の例では background
が dark
か light
かで色の分岐を行なっていますし、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では gnus
や paren
というパッケージに対応していることが分かります。
このようにカラーテーマごとに「対応しているパッケージ」が異なっており、メンテナンスされていないカラーテーマを使っていると、パッケージが決めたデフォルトの色がそのまま表示されることになります。
そのため「色が暗い」「色がビビットすぎる」「そもそも見えない」などの問題が発生します。
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-d
や yellow-l
という色も定義されており、第3引数で使用できます。
これらは solarized-create-color-palette
という関数の中で自動生成されています。
完成形
本家のiceberg.vimのスクショと比較しながら、間違っている face
を describe-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生活がよりよりものになれば良いなと思っています。