5分で学ぶ！Atomテーマ（Syntax Theme）自作入門

## はじめに

[Atomアドベントカレンダー](https://qiita.com/advent-calendar/2017/atom)24日目の記事（飛び入り参加 3日ぶり2回目）です。

先日[Atomパッケージの自作](https://qiita.com/akisame338/items/034476debbd0ae251c0f)についての記事を書きましたが、今回はAtomテーマ（Syntax Theme）の自作方法を紹介していきます。

## Atomのテーマについて

Atomのテーマには**UI Theme**と**Syntax Theme**の2種類があります。

UI Themeはツリービューやタブなどの見た目に、Syntax Themeは主にテキストエディタ部分のコードの見た目にそれぞれ影響を与えます。
この2つの違いについては、[公式ドキュメント](http://flight-manual.atom.io/hacking-atom/sections/creating-a-theme/#creating-a-theme)の画像を見るとイメージしやすいかと思います。

この記事では、この2つのうちSyntax Themeの自作方法を紹介していきます。

## テーマの雛形を生成

まず、テーマの雛形を生成します。

パッケージ作成の時と同様、コマンドパレットから`Package Generator: Generate Syntax Theme`を実行することでSyntax Themeの雛形が作成できます。
こちらも作成するテーマのパスの入力を求められますが、今回はデフォルトの`~/github/my-theme-syntax`のまま進めていきます。

※[公式ドキュメント](http://flight-manual.atom.io/hacking-atom/sections/creating-a-theme/#creating-a-syntax-theme)によると、Syntax Themeの場合はパスの末尾を`-syntax`とするべきだそうです。

> Tip: Syntax themes should end with -syntax and UI themes should end with -ui.

パスを入力してテーマの雛形の生成が完了すると、`~/github/my-theme-syntax`ディレクトリが生成され、`~/.atom/packages`にこのパスへのシンボリックリンクが貼られます。

```console:~/.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`というテーマ名として扱われるようです。）

![スクリーンショット 2017-12-23 2.29.12.png](https://qiita-image-store.s3.amazonaws.com/0/125531/363bde55-fe60-0375-ca50-a2819d124352.png)

なお、作成したテーマの雛形のディレクトリ（`~/github/my-theme-syntax`）以下には、下記のようなファイル・ディレクトリが生成されています。

```console:~/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

```json: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

```less:index.less
@import "./styles/base.less";
```

### styles/base.less

```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

```less: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

```less: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）の作成については

1. 自分が作りたいテーマのイメージカラーをそのままLESS（CSS）に落とし込み
2. 適当なコード（後述）に対する見た目がいい感じになるまで試行錯誤を繰り返す

というイメージです。

今回は例として以下のようなカラーリングのテーマを作成してみます。

* コメント: <font style="color: #464b4f;">黒</font>
* `function`（アロー関数）: <font style="color: #fa98bf;">ピンク</font>
* `class`: <font style="color: #b51d66;">臙脂</font>
* クラス: <font style="color: #fcd424">黄色</font>
* `get`, `new`: <font style="color: #a1ca62;">黄緑</font>
* 関数: <font style="color: #0775c4;">青</font>
* `return`: <font style="color: #7e51a6;">紫</font>
* 文字列: <font style="color: #00a752;">緑</font>
* 変数: <font style="color: #f70f1f;">赤</font>
* `static`, `const`: <font style="color: #aececb;">白</font>
* 標準関数: <font style="color: #00b1bb;">浅葱</font>
* 定数: <font style="color: #f29047;">オレンジ</font>

参考までに、テーマの見た目の確認用として以下のようなjsのコードを使用しました。

```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-`という接頭辞を付与して競合を回避しました。

```diff:styles/colors.less
 @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`に構文に関する変数を定義していきます。

実際に進めていった手順としては

1. Atom上で表示されるソースコードについて、デベロッパーツールでDOM構造を眺めつつ
2. 任意色を反映させたい要素に対して`styles/base.less`の定義に合わせてそれっぽい変数名を付けていく

といった感じでした。

```diff
 @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`にスタイルの定義を反映させていきます。

```diff: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;
   }
```

## テーマ修正前後でのコードの見た目

テーマを修正する前後でのコードの見た目はそれぞれ以下のような感じです。

### テーマ修正前

![スクリーンショット 2017-12-23 7.42.01.png](https://qiita-image-store.s3.amazonaws.com/0/125531/982ed0bd-f209-3958-0911-307db1057d0c.png)

全体的に落ち着いた色合いでまとまっている印象です。

### テーマ修正後

![スクリーンショット 2017-12-23 7.40.58.png](https://qiita-image-store.s3.amazonaws.com/0/125531/e42005e3-22e0-285c-c98e-577d46bf1c2e.png)

とてもカラフルな見た目に変わりました！

## おわりに

駆け足でしたが、Atomのテーマ（Syntax Theme）の自作方法について紹介していきました。

やってみた感想としては、コードの見た目を確認しつつ適宜スタイル定義を変更していくだけなので、パッケージ自作の時と同様**「思っていた以上にお手軽に自作できてしまう」**という印象でした！
（やろうと思えば非常に細かくスタイル定義ができるので、凝ったものを作ろうと思えば、もちろんそれ相応に大変だとは思います。）

「Atomの見た目をこんな風に変えてみたい！」というイメージさえあれば、あとはLESS（CSS）をガリガリ書いていくだけなので、もし興味がある方は試してみてはいかがでしょうか？

いずれちゃんとしたテーマを作成して公開できればと思っております。
あと、時間に余裕があればUI Themeの方の自作にも挑戦してみたいです。

## 参考記事

* [Atom Documentation > Atom Flight Manual > Chapter 3: Hacking Atom > Creating a Theme](http://flight-manual.atom.io/hacking-atom/sections/creating-a-theme/)
* [CSSでできる！Atomエディターのテーマをカスタマイズする方法 - WPJ](https://www.webprofessional.jp/build-your-own-atom-theme-with-css/)
*