はじめに
Atomアドベントカレンダー24日目の記事(飛び入り参加 3日ぶり2回目)です。
先日Atomパッケージの自作についての記事を書きましたが、今回はAtomテーマ(Syntax Theme)の自作方法を紹介していきます。
Atomのテーマについて
AtomのテーマにはUI ThemeとSyntax Themeの2種類があります。
UI Themeはツリービューやタブなどの見た目に、Syntax Themeは主にテキストエディタ部分のコードの見た目にそれぞれ影響を与えます。
この2つの違いについては、公式ドキュメントの画像を見るとイメージしやすいかと思います。
この記事では、この2つのうちSyntax Themeの自作方法を紹介していきます。
テーマの雛形を生成
まず、テーマの雛形を生成します。
パッケージ作成の時と同様、コマンドパレットからPackage Generator: Generate Syntax Theme
を実行することでSyntax Themeの雛形が作成できます。
こちらも作成するテーマのパスの入力を求められますが、今回はデフォルトの~/github/my-theme-syntax
のまま進めていきます。
※公式ドキュメントによると、Syntax Themeの場合はパスの末尾を-syntax
とするべきだそうです。
Tip: Syntax themes should end with -syntax and UI themes should end with -ui.
パスを入力してテーマの雛形の生成が完了すると、~/github/my-theme-syntax
ディレクトリが生成され、~/.atom/packages
にこのパスへのシンボリックリンクが貼られます。
...
my-theme-syntax -> /PATH/TO/~/github/my-theme-syntax
...
~/.atom/packages
以下にシンボリックリンクが貼られることにより、自作のテーマ(my-theme-syntax
)がインストール済みのSyntax Themeとして扱われ、Settings > Themes > Choose a Theme > Syntax Theme
から選択できるようになります。(my-theme-syntax
という名前の場合、My
というテーマ名として扱われるようです。)
なお、作成したテーマの雛形のディレクトリ(~/github/my-theme-syntax
)以下には、下記のようなファイル・ディレクトリが生成されています。
.
├── index.less
├── package.json
└── styles
├── base.less
├── colors.less
└── syntax-variables.less
各ファイルの役割はざっくり説明すると以下の通りです。
-
package.json
: テーマの名前や説明などの情報を定義するファイル -
index.less
: メインのスタイルシート(実態はstyles/base.less
をインポートしているだけ) -
styles/base.less
: エディタ部分のスタイルを定義する、ベースとなるスタイルシート(styles/syntax-variables.less
をインポートしている) -
styles/syntax-variables.less
:styles/base.less
で使用する、構文に関する変数を定義するスタイルシート(styles/colors.less
をインポートしている) -
styles/colors.less
:styles/base.less
,styles/syntax-variables.less
で使用する、色に関する変数を定義するスタイルシート
各ファイルの内容は、デフォルトではそれぞれ以下のようになっています。
package.json
{
"name": "my-theme-syntax",
"theme": "syntax",
"version": "0.0.0",
"description": "A short description of your syntax theme",
"keywords": [
"syntax",
"theme"
],
"repository": "https://github.com/atom/my-theme-syntax",
"license": "MIT",
"engines": {
"atom": ">=1.0.0 <2.0.0"
}
}
index.less
@import "./styles/base.less";
styles/base.less
@import "syntax-variables";
atom-text-editor {
background-color: @syntax-background-color;
color: @syntax-text-color;
.wrap-guide {
background-color: @syntax-wrap-guide-color;
}
.indent-guide {
color: @syntax-indent-guide-color;
}
.invisible-character {
color: @syntax-invisible-character-color;
}
.gutter {
background-color: @syntax-gutter-background-color;
color: @syntax-gutter-text-color;
.line-number {
&.cursor-line {
background-color: @syntax-gutter-background-color-selected;
color: @syntax-gutter-text-color-selected;
}
&.cursor-line-no-selection {
color: @syntax-gutter-text-color-selected;
}
}
}
.gutter .line-number.folded,
.gutter .line-number:after,
.fold-marker:after {
color: @light-gray;
}
.invisible {
color: @syntax-text-color;
}
.cursor {
color: @syntax-cursor-color;
}
.selection .region {
background-color: @syntax-selection-color;
}
}
// Syntax styles
.syntax--comment {
color: @light-gray;
}
.syntax--keyword {
color: @purple;
&.syntax--control {
color: @purple;
}
&.syntax--operator {
color: @syntax-text-color;
}
&.syntax--other.syntax--special-method {
color: @blue;
}
&.syntax--other.syntax--unit {
color: @orange;
}
}
.syntax--storage {
color: @purple;
}
.syntax--constant {
color: @orange;
&.syntax--character.syntax--escape {
color: @cyan;
}
&.syntax--numeric {
color: @orange;
}
&.syntax--other.syntax--color {
color: @cyan;
}
&.syntax--other.syntax--symbol {
color: @green;
}
}
.syntax--variable {
color: @red;
&.syntax--interpolation {
color: darken(@red, 10%);
}
&.syntax--parameter.syntax--function {
color: @syntax-text-color;
}
}
.syntax--invalid.syntax--illegal {
background-color: @red;
color: @syntax-background-color;
}
.syntax--string {
color: @green;
&.syntax--regexp {
color: @cyan;
.syntax--source.syntax--ruby.syntax--embedded {
color: @orange;
}
}
&.syntax--other.syntax--link {
color: @red;
}
}
.syntax--punctuation {
&.syntax--definition {
&.syntax--comment {
color: @light-gray;
}
&.syntax--string,
&.syntax--variable,
&.syntax--parameters,
&.syntax--array {
color: @syntax-text-color;
}
&.syntax--heading,
&.syntax--identity {
color: @blue;
}
&.syntax--bold {
color: @light-orange;
font-weight: bold;
}
&.syntax--italic {
color: @purple;
font-style: italic;
}
}
&.syntax--section.syntax--embedded {
color: darken(@red, 10%);
}
}
.syntax--support {
&.syntax--class {
color: @light-orange;
}
&.syntax--function {
color: @cyan;
&.syntax--any-method {
color: @blue;
}
}
}
.syntax--entity {
&.syntax--name.syntax--function {
color: @blue;
}
&.syntax--name.syntax--type {
color: @light-orange;
text-decoration: underline;
}
&.syntax--other.syntax--inherited-class {
color: @green;
}
&.syntax--name.syntax--class, &.syntax--name.syntax--type.syntax--class {
color: @light-orange;
}
&.syntax--name.syntax--section {
color: @blue;
}
&.syntax--name.syntax--tag {
color: @red;
text-decoration: underline;
}
&.syntax--other.syntax--attribute-name {
color: @orange;
&.syntax--id {
color: @blue;
}
}
}
.syntax--meta {
&.syntax--class {
color: @light-orange;
}
&.syntax--link {
color: @orange;
}
&.syntax--require {
color: @blue;
}
&.syntax--selector {
color: @purple;
}
&.syntax--separator {
background-color: @gray;
color: @syntax-text-color;
}
}
.syntax--none {
color: @syntax-text-color;
}
.syntax--markup {
&.syntax--bold {
color: @orange;
font-weight: bold;
}
&.syntax--changed {
color: @purple;
}
&.syntax--deleted {
color: @red;
}
&.syntax--italic {
color: @purple;
font-style: italic;
}
&.syntax--heading .syntax--punctuation.syntax--definition.syntax--heading {
color: @blue;
}
&.syntax--inserted {
color: @green;
}
&.syntax--list {
color: @red;
}
&.syntax--quote {
color: @orange;
}
&.syntax--raw.syntax--inline {
color: @green;
}
}
.syntax--source.syntax--gfm .syntax--markup {
-webkit-font-smoothing: auto;
&.syntax--heading {
color: @green;
}
}
// Mini editor
atom-text-editor[mini] .scroll-view {
padding-left: 1px;
}
styles/syntax-variables.less
@import "colors";
// This defines all syntax variables that syntax themes must implement when they
// include a syntax-variables.less file.
// General colors
@syntax-text-color: @very-light-gray;
@syntax-cursor-color: white;
@syntax-selection-color: lighten(@dark-gray, 10%);
@syntax-background-color: @very-dark-gray;
// Guide colors
@syntax-wrap-guide-color: @dark-gray;
@syntax-indent-guide-color: @gray;
@syntax-invisible-character-color: @gray;
// For find and replace markers
@syntax-result-marker-color: @light-gray;
@syntax-result-marker-color-selected: white;
// Gutter colors
@syntax-gutter-text-color: @very-light-gray;
@syntax-gutter-text-color-selected: @syntax-gutter-text-color;
@syntax-gutter-background-color: @dark-gray;
@syntax-gutter-background-color-selected: @gray;
// For git diff info. i.e. in the gutter
@syntax-color-renamed: @blue;
@syntax-color-added: @green;
@syntax-color-modified: @orange;
@syntax-color-removed: @red;
styles/colors.less
// These colors are specific to the theme. Do not use in a package!
@very-light-gray: #c5c8c6;
@light-gray: #969896;
@gray: #373b41;
@dark-gray: #282a2e;
@very-dark-gray: #1d1f21;
@cyan: #8abeb7;
@blue: #81a2be;
@purple: #b294bb;
@green: #b5bd68;
@red: #cc6666;
@orange: #de935f;
@light-orange: #f0c674;
テーマの作成
ここからは実際にテーマを作成していきます。
…といっても、テーマ(Syntax Theme)の作成については
- 自分が作りたいテーマのイメージカラーをそのままLESS(CSS)に落とし込み
- 適当なコード(後述)に対する見た目がいい感じになるまで試行錯誤を繰り返す
というイメージです。
今回は例として以下のようなカラーリングのテーマを作成してみます。
- コメント: 黒
-
function
(アロー関数): ピンク -
class
: 臙脂 - クラス: 黄色
-
get
,new
: 黄緑 - 関数: 青
-
return
: 紫 - 文字列: 緑
- 変数: 赤
-
static
,const
: 白 - 標準関数: 浅葱
- 定数: オレンジ
参考までに、テーマの見た目の確認用として以下のようなjsのコードを使用しました。
/**
* Syntax Theme Test
*/
(() => {
class Hoge {
static get STATIC_MESSAGE() {
return 'HOGEHOGE';
}
constructor(msg) {
this.msg = msg;
}
get replacedMessage() {
return this.msg.replace(/^(.)(.+)(.)$/, (match, p1, p2, p3) => {
return `${p1.toUpperCase()}${p2}${p3.toUpperCase()}`;
});
}
}
const hoge = new Hoge('hogehoge');
console.log(Hoge.STATIC_MESSAGE);
// HOGEHOGE
console.log(hoge.replacedMessage());
// HogehogE
})();
各ファイルの修正内容はそれぞれ以下の通りです。
※LESS(CSS)のお作法に詳しくないため、変数定義が非常に雑です。「もっといいやり方があるよ!」とか「ここはこうした方がいいよ!」といったコメント大歓迎です。
styles/colors.less
まずはstyles/colors.less
に色に関する変数を定義していきます。
@red
や@blue
などの変数名は既に使用されていたため、自作テーマの変数にはmy-
という接頭辞を付与して競合を回避しました。
@light-orange: #f0c674;
+
+@my-red: #f70f1f;
+@my-blue: #0775c4;
+@my-white: #aececb;
+@my-orange: #f29047;
+@my-green: #00a752;
+@my-purple: #7e51a6;
+@my-pink: #fa98bf;
+@my-black: #464b4f;
+@my-yellow: #fcd424;
+@my-fresh-green: #a1ca62;
+@my-pale-blue-green: #00b1bb;
+@my-carmine: #b51d66;
styles/syntax-variables.less
次にstyles/syntax-variables.less
に構文に関する変数を定義していきます。
実際に進めていった手順としては
- Atom上で表示されるソースコードについて、デベロッパーツールでDOM構造を眺めつつ
- 任意色を反映させたい要素に対して
styles/base.less
の定義に合わせてそれっぽい変数名を付けていく
といった感じでした。
@syntax-background-color: @very-dark-gray;
+@syntax-comment-color: @my-black;
+@syntax-keyword-control-color: @my-purple;
+@syntax-keyword-operator-color: @my-fresh-green;
+@syntax-storage-color: @my-white;
+@syntax-storage-class-color: @my-carmine;
+@syntax-storage-function-rolor: @my-pink;
+@syntax-constant-color: @my-orange;
+@syntax-variable-rolor: @my-red;
+@syntax-string-color: @my-green;
+@syntax-support-function-color: @my-pale-blue-green;
+@syntax-entity-name-function-color: @my-blue;
+@syntax-entity-name-class-color: @my-yellow;
+
// Guide colors
styles/base.less
そして、styles/base.less
にスタイルの定義を反映させていきます。
.syntax--comment {
- color: @light-gray;
+ color: @syntax-comment-color;
}
.syntax--keyword {
color: @purple;
&.syntax--control {
- color: @purple;
+ color: @syntax-keyword-control-color;
}
&.syntax--operator {
color: @syntax-text-color;
+
+ &.syntax--new, &.syntax--getter {
+ color: @syntax-keyword-operator-color;
+ }
}
...
.syntax--storage {
- color: @purple;
+ color: @syntax-storage-color;
+
+ &.syntax--class {
+ color: @syntax-storage-class-color;
+ }
+
+ &.syntax--function {
+ color: @syntax-storage-function-rolor;
+ }
}
.syntax--constant {
- color: @orange;
+ color: @syntax-constant-color;
...
.syntax--variable {
- color: @red;
+ color: @syntax-variable-rolor;
&.syntax--interpolation {
color: darken(@red, 10%);
...
.syntax--string {
- color: @green;
+ color: @syntax-string-color;
...
.syntax--punctuation {
&.syntax--definition {
&.syntax--comment {
- color: @light-gray;
+ color: @syntax-comment-color;
}
...
&.syntax--function {
- color: @cyan;
+ color: @syntax-support-function-color;
...
.syntax--entity {
&.syntax--name.syntax--function {
- color: @blue;
+ color: @syntax-entity-name-function-color;
}
...
- &.syntax--name.syntax--class, &.syntax--name.syntax--type.syntax--class {
- color: @light-orange;
+ &.syntax--name.syntax--class, &.syntax--name.syntax--type.syntax--class, &.syntax--name.syntax--instance {
+ color: @syntax-entity-name-class-color;
}
テーマ修正前後でのコードの見た目
テーマを修正する前後でのコードの見た目はそれぞれ以下のような感じです。
テーマ修正前
全体的に落ち着いた色合いでまとまっている印象です。
テーマ修正後
とてもカラフルな見た目に変わりました!
おわりに
駆け足でしたが、Atomのテーマ(Syntax Theme)の自作方法について紹介していきました。
やってみた感想としては、コードの見た目を確認しつつ適宜スタイル定義を変更していくだけなので、パッケージ自作の時と同様**「思っていた以上にお手軽に自作できてしまう」**という印象でした!
(やろうと思えば非常に細かくスタイル定義ができるので、凝ったものを作ろうと思えば、もちろんそれ相応に大変だとは思います。)
「Atomの見た目をこんな風に変えてみたい!」というイメージさえあれば、あとはLESS(CSS)をガリガリ書いていくだけなので、もし興味がある方は試してみてはいかがでしょうか?
いずれちゃんとしたテーマを作成して公開できればと思っております。
あと、時間に余裕があればUI Themeの方の自作にも挑戦してみたいです。