はてなブログのソースコードに行番号を表示する

他の Syntax Highlight の記法は使えない

Javascriptで処理してCSScounter-incrementを使って、はてなブログソースコードに行番号を表示させます。あくまでMarkdown記法が前提です。

普段 Hugo の Markdown で記事を書いているので、そのSyntax Highlightの書き方と同じ記法で行番号を表示させたいですが無理です。根本的な問題はSyntax Highlightのコード色付けそのものが効かなくなることです。これでは意味がありません。

Hugo のSyntax Highlightで行番号を表示するには言語名の指定に続けて{linenos=table}のように書きます。例えばpythonなら次のような形です。

 ```python {linenos=table}  
  ...ソースコード... 
 ```

この{linenos=table}の部分で行番号を表示するのですが、Hugo とはてなブログでは Markdownパーサが違うので、はてなブログでは当然何も変わりません。幸いなことに、はてなブログMarkdownでは、この部分はまるごとクラス名になって、ブラウザ画面上では表示されません。つまりよくありがちなパーサが解釈できない部分を文字列としてデロっと表示したりはしないようです。

実際のHTML上では次のようになってます。

<pre class="code python {linenos=table}" data-lang="python {linenos=table}" data-unlink>

..........ソースコードの内容........

</pre>

ところが問題があって、普通なら言語指定の部分が<pre class="code lang-python" data-lang="python" data-unlink>となるべきところ、このlang-が消えてしまいます。おそらく Markdownパーサがコードブロックを処理する前にこの改変部分が邪魔になるので、Syntax Highlightの処理がされなくなってしまうのだと思われます。

全てのソースコードに行番号を表示させたいなら、余計な記法を付加する必要は無いので何も問題ないのですが、あるコードには行番号を付けて、ある番号には付けないといった分岐をさせたい場合、どうやって区別するかが問題です。ここが注意点で、おそらく他の方がやっておられるJavascriptのコードは、そのままでは使えないと思われ工夫が必要です。

はてなブログMarkdownパーサが何というものかは知りませんが、おそらく他所で使える独自仕様の行番号表示の記法でも、Syntax Highlightの言語名指定の部分を弄る場合は同じことだと思います。

独自クラスのDIVタグで囲む

いろいろ考えてもいい方法が思いつかず、結局ありきたりですが独自クラスのDIVタグで囲むという方法におちつきました。

こんな感じになります。

【表示結果】
def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

code-linenosクラスの付いたDIVで囲まれたソースコードのみ、行番号を表示する形です。もちろんこのクラス名は勝手に決めたもので任意でかまいません。

<div class="code-linenos">
 ```python
 def ask_ok(prompt, retries=4, reminder='Please try again!'):
     while True:
         ok = input(prompt)
         if ok in ('y', 'ye', 'yes'):
             return True
         if ok in ('n', 'no', 'nop', 'nope'):
             return False
         retries = retries - 1
         if retries < 0:
             raise ValueError('invalid user response')
         print(reminder)
  ```
</div>

Javascriptでの処理

こちらの方法を参考にさせていただいたのですが、行番号を表示したりしなかったりしたかったので、Javascriptのコードはかなり違ってます。

codelife.cafe

以下のコードをscriptタグで囲んで、管理画面の「デザイン」からフッターに書き込みます。

/* Add line number to source code */

const addLineNumber = (block) => {
  let source_code = block.innerHTML.slice(-1) === '\n' ? block.innerHTML.slice(0, -1) : block.innerHTML;
  let lines = source_code.split(/\n/);
  let code_block = "";
  lines.forEach(function(line){
    line += line === '' ? '\n' : '';
    code_block += '<div class="code-line">' + line + '</div>';
  })
  block.innerHTML = code_block;
}

document.addEventListener('DOMContentLoaded', (event) => {
  const codeBlocks = document.getElementsByClassName('code-linenos');
  codeBlocks.forEach((block)=>{
    addLineNumber(block.firstElementChild);
  });
}, false);

CSSの処理

同じく管理画面の「デザイン」からカスタムCSSに以下を書き込みます。

/* Style add line number of source code */
.code-line {
  counter-increment: linenumber;
}

.code-line::before {
  content: counter(linenumber);
  display:inline-block;
  color: yellowgreen;
  text-align: right;
  width: 35px;
  margin-right: 1.0em;
  padding: 0 0.5em 0 0;
  border-right: 1px solid #333;
}

一応3桁の行番号に対応できる幅にしました。3桁なんて使わないと思いますが(笑)

Hugo の Markdown で書いた記事とは多少記法を変える必要があり、面倒ですが仕方ありません。ただ、行番号を表示したくない場合はcode-linenosクラス付きのDIVで囲まなければいいだけなので分岐できます。