[Kotlin][WordPress] WordPressのSyntax Highlighter Evolved用 Kotlinプラグインを作った #ktac2015

2015.12.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。こむろです。札幌は(色んな意味で)試されております *1

本エントリーは Kotlin Advent Calendar の3日目です。

昨日は、@takuji31さんみんな大好きKotlinのDelegationについて #ktac2015でした。

WordPress Syntax Highlighter Plugin

今回はWordpress の Syntax Highlighter Evolved の Kotlin プラグインを作成してみました。本記事ではプラグインの作成方法と、Kotlin のプラグインを作ったよという紹介です。

Kotlin の技術的な話はほぼありませんが、Kotlin に関する記事執筆するためのサポート機能を作ってみた、ということでご容赦ください。

はじめに

まず初めにWordpressで利用しているSyntax Highlighterプラグインが何かを特定します。Developers.IOの場合は、SyntaxHighlighter Evolved を利用しています。こちらを対象にした言語の追加プラグインを作成します。

配色について

「黒ベースのDarkなテーマなのか」、「白ベースのLightなテーマなのか」、念の為こちらを確認しました。ソースコードに色をつけるにおいて、どのような配色にするのかイメージを明確にするためテーマを確認したうえで色を決めるために必要かと思います。全体的なソースコードの配色イメージは決めておいた方が良いかと。

こんな色でハイライトされればそれなりに見やすいのではないでしょうか。

Kotlin Syntax Highlight Color

Kotlin で色付けしたい要素を洗い出す

Kotlin は Java と似たようなシンタックスを持ちますが、当然Javaにはない独自の特殊な構文も数多く存在します。

  • 関数を宣言するための fun
  • 定数宣言の val, 変数宣言の var
  • static 変数を宣言したりするときに使う companion object

(自分が使ったことのある)代表的なものを上げてみました。これらは Kotlin 由来のシンタックスとして、色付けしてあげる必要がありそうです。こちらのリンクを参照にひたすらキーワードを探しながら

Kotlin 言語プラグインを作成する前に

SyntaxHighlighter Evolved のgithubブランチを Fork します。

4.0が最新版ブランチのようですが、今回は master ブランチを Fork しました。

スクリーンショット 2015-11-29 16.44.38

Fork後

スクリーンショット 2015-11-29 16.44.59

自分のアカウント以下にForkされたのが確認できました。

中身を確認してみます。公式の言語プラグイン定義は以下のディレクトリにあるようです。

  • syntaxhighlighter2/scripts
  • syntaxhighlighter3/scripts

Brush という Javascript ファイルが確認できます。これがプラグインの言語定義のようです。Brushファイルはそれぞれ Java, JavaFX, C, PHP などが公式な言語プラグインとしてすでに定義ファイルが確認できます。

公式以外(?)の言語プラグインは以下に配置されているようです。

  • third-party-brushes

こちらに Objective-CClojure などの言語定義が確認できます。今回は、こちらに Kotlin のプラグ定義を追加作成します。

プラグインを作成する

追加の言語定義は以下の手順で作成・追加します。

  1. 言語の定義となるBrushファイル(javascript)を作成する
  2. syntaxhighlighter.php に追加したい言語のBrushファイルとエイリアスを設定する

ブランチ作成

新たに言語定義ファイルを追加するため git の新ブランチを作成します。

$ git checkout -b feature/add_kotlin_syntax

以後こちらのブランチで作業していきます。

プラグインファイルの作成

third-party-brushes 以下に shBrushKotlin.js を作成します。ファイル名の規則は念の為他のBrushファイルに従いました。 *2

スクリーンショット 2015-11-13 14.21.48

third-party-brushes の中に shBrushKotlin.js を作成します。今回は例として、 shBrushClojure.js を参考にひな形を作成します。

SyntaxHighlighter.brushes.Kotlin = function()
{
  // Define Kotlin Syntax
}

SyntaxHighlighter.brushes.Kotlin.prototype     = new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes.Kotlin.aliases       = ['kotlin', 'Kotlin', 'kt'];

SyntaxHighlighter.brushes.Kotlin を定義します。 *3

Kotlin のショートコードを実行するためのエイリアスを3つに設定しました。すべて小文字の kotlin、先頭のみ大文字の Kotlin、そしてファイル拡張子でもある kt です。

シンタックスの定義

基本的なシンタックスをグループ分けします。

弊社のブログのテーマが暗めなのでそちらをベースに配色を考えました。

  • コメント: ///* */ のスタイルに対応させています
  • var, val, fun: 変数, 定数, 関数の定義
  • 文字列: "" で囲まれた文字列の定義
  • その他キーワード:アクセス修飾子、クラス、インタフェースなど

基本の文字色+定義された4色の計5色で構成されるようにしました。

SyntaxHighlighter.brushes.Kotlin = function()
{
    // var, val
    var vals = "var val";
    // function
    var functions = "fun";
    // other syntax 
    var keywords = "package class object property import " + 
                   "interface constructor by where init companion " + 
                   "get set this super dynamic " + 
                   "if else try catch finally for do while " + 
                   "throw return continue break when in out is " + 
                   "abstract final enum open annotation override " + 
                   "private protected public internal file field param sparam";
}

空白区切りの文字列として、検出が必要な単語を列挙します。

当初、keyword の中に「$ @」を入れていましたが、こちらはいれてしまうと無限ループに陥ってしまうようです。特殊な記号なので気づきそうなものですが、気づかずにぶち込んでしまい、プラグイン導入を担当してくれた 諏訪 に怒られました。ご注意を。これらの文字はきちんと正規表現として個別に設定してあげる必要があります。

正規表現リストを追加する

先ほど作成したシンタックスグループを検出するための、正規表現リストを作成します。

this.regexList = [
    { regex: SyntaxHighlighter.regexLib.singleLineCComments,    css: 'comment' },     // one line comments
    { regex: /\/\*([^\*][\s\S]*?)?\*\//gm,                      css: 'comments' },        // multiline comments
    { regex: /\/\*(?!\*\/)\*[\s\S]*?\*\//gm,                    css: 'preprocessor' },    // documentation comments
    { regex: SyntaxHighlighter.regexLib.doubleQuotedString,     css: 'string' },      // double quoted strings
    { regex: SyntaxHighlighter.regexLib.singleQuotedString,     css: 'string' },      // single quoted strings
    { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi,             css: 'value' },           // numbers
    { regex: new RegExp(this.getKeywords(functions), 'gm'),       css: 'functions' },       // function
    { regex: new RegExp(this.getKeywords(vals), 'gm'),            css: 'variable' },        // var, val
    { regex: new RegExp('@\\w+\\b', 'g'),                       css: 'keyword' },
    { regex: new RegExp(this.getKeywords(keywords), 'gm'),        css: 'keyword' }      // kotlin keyword
];

上から3つ comment, comments, preprocessor の定義はJavaの定義ファイルから拝借してきました。

文字列の定義もJavaと対して変わらないため、 double quoted strings, single quoted strings こちらも拝借。

数値の定義も変わらないため以下略。

fun に適用したシンタックスグループには、cssの functions という要素を適用します。

.syntaxhighlighter.printing .functions {
  color: #ff1493 !important;
}

var, val のシンタックスグループは、 cssの variable 要素を適用します。

.syntaxhighlighter.printing .variable {
  color: #aa7700 !important;
}

最後に一番大きなシンタックスグループのkeywords には、 cssのkeyword を適用します。

.syntaxhighlighter.printing .keyword {
  color: #006699 !important;
  font-weight: bold !important;
}

いずれの定義も syntaxhighlighter3/styles/shCore.css に記述されています。これらの要素は選択されたテーマによって上書きされる可能性があります。

独自のシンタックスハイライトの色を定義したい場合は、こちらのshCore.css に定義を記載し、それぞれのテーマに応じても色を定義する必要があるかと思います。今回は新たに色を追加する予定はないので、cssファイルに手は加えていません。

gt, ltをエスケープ

< 及び > はエスケープして上げる必要があります。先ほどの正規表現リストの下に追記

this.forHtmlScript({
    left    : /(<|<)%[@!=]?/g, 
    right   : /%(>|>)/g 
});

定義に関してはこれで全部完了です。全体はこちら

SyntaxHighlighter.brushes.Kotlin = function()
{
    // var, val
    var vals = "var val";
    // function
    var functions = "fun";
    // syntax 
    var keywords = "package class object property import " + 
                   "interface constructor by where init companion " + 
                   "get set this super dynamic " + 
                   "if else try catch finally for do while " + 
                   "throw return continue break when in out is " + 
                   "abstract final enum open annotation override " + 
                   "private protected public internal file field param sparam";

    this.regexList = [
        { regex: SyntaxHighlighter.regexLib.singleLineCComments,    css: 'comment' },     // one line comments
        { regex: /\/\*([^\*][\s\S]*?)?\*\//gm,                      css: 'comments' },        // multiline comments
        { regex: /\/\*(?!\*\/)\*[\s\S]*?\*\//gm,                    css: 'preprocessor' },    // documentation comments
        { regex: SyntaxHighlighter.regexLib.doubleQuotedString,     css: 'string' },      // double quoted strings
        { regex: SyntaxHighlighter.regexLib.singleQuotedString,     css: 'string' },      // single quoted strings
        { regex: /\b([\d]+(\.[\d]+)?|0x[a-f0-9]+)\b/gi,             css: 'value' },           // numbers
        { regex: new RegExp(this.getKeywords(functions), 'gm'),       css: 'functions' },       // function
        { regex: new RegExp(this.getKeywords(vals), 'gm'),            css: 'variable' },        // var, val
        { regex: new RegExp('@\\w+\\b', 'g'),                       css: 'keyword' },
        { regex: new RegExp(this.getKeywords(keywords), 'gm'),        css: 'keyword' }      // kotlin keyword
    ];

    this.forHtmlScript({
        left    : /(<|<)%[@!=]?/g, 
        right   : /%(>|>)/g 
    });
}

SyntaxHighlighter.brushes.Kotlin.prototype     = new SyntaxHighlighter.Highlighter();
SyntaxHighlighter.brushes.Kotlin.aliases       = ['kotlin', 'Kotlin', 'kt'];

設定

syntaxhighlighter.php こちらにBrushファイルの設定を記述します。

今回は、syntaxhighlighter以下の third-party-brushes/shBrushKotlin.js にファイルを作成したので、以下のように記述。

// Add Kotlin Brush
wp_register_script( `SyntaxHighlighter-brush-kotlin`,     plugins_url( `third-party-brushes/shBrushKotlin.js`,            __FILE__),  array(`syntaxhighlighter-core`), `20151120`     );

適当な箇所にBrushファイルの登録処理を追記します。続いてショートコードとBrushの対応付。

$this->brushes = (array) apply_filters( 'syntaxhighlighter_brushes', array(
...)

ここで、ショートコードの定義を行っているようなのでKotlinを追加します。

$brushes['kotlin'] = 'kotlin';
$brushes['kt']     = 'kotlin';

kotlin 及び kt は今回作成したKotlin用のBrushが適用されるようにしました。

確認

確認してみました。

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
    if (!mNavigationDrawerFragment!!.isDrawerOpen()) {
        // Only show items in the action bar relevant to this screen
        // if the drawer is not showing. Otherwise, let the drawer
        // decide what to show in the action bar.
        getMenuInflater().inflate(R.menu.main, menu)
        restoreActionBar()
        return true
    }
    return super<Activity>.onCreateOptionsMenu(menu)
}

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    val id = item!!.getItemId()

    //noinspection SimplifiableIfStatement
    if (id == R.id.action_settings) {
        return true
    }

    return super<Activity>.onOptionsItemSelected(item)
}

わかりづらいですが、ショートコードkotlinで囲っています。作成したBrushが適用されてるようです(あれ・・・。val にCSSが適用されていない気が・・・) *4

まとめ

今まで Developers.IO で Kotlin のブログ記事を何本か投稿してはいたのですが、ソースコードをハイライトしてくれるプラグインは Kotlin 未対応でした。

そのため、毎度 Java のハイライトを適用して書いてみたり Groovy のハイライトを適用してたりしましたが、あまりにソースコードに色がつかなすぎてとても無味乾燥なものに見えてしまい、悩んだ結果あえなく自作と相成りました。

おそらく Kotlin という言語仕様自体がまだまだドラスティックに変わっている最中なので、公式でサポートされるには、言語仕様が固まるまでは様子見なのかな(もしくはユーザー数の増大)と思っています。

今回作成したプラグインも、言語仕様の変更がまだあり得るのでいつまで有効かは分かりませんが、細々とメンテナンスしていこうかと思います。Wordpress を利用していて、ソースのハイライトに Syntax Highlighter Evolved を使っている方はご利用頂けれるかと思います。また間違いや修正などもご指摘いただけるととても助かります。

ここ最近Android+Kotlinもすっかりご無沙汰なので、この機会に折角なので投稿を再開していきたいと思います。

明日は daneko0123さん です。

参照

脚注

  1. 2015/12/03は雨!雪が溶けて天然のスケートリンクが生成されていく!
  2. 郷に入れば郷に従え精神
  3. 他の言語定義を見ると、微妙にそれぞれ記述フォーマットが違っていて何が正解かがいまいちわかっていません。。。
  4. ちゃんと治します