markdown-modeで“GitHub Flavored Markdown”を実現する

# TL;DR

こちらが現時点（2020年2月）での最適解となります。
（@yewton さんの[コメント](#comment-108aa02f7ba119fb8fdf)より。ありがとうございます！)

```shell-session
$ gem install commonmarker
```

```elisp:init.el
(setq markdown-command "commonmarker --extension=autolink,strikethrough,table,tagfilter,tasklist"
      markdown-css-paths '("https://cdn.jsdelivr.net/npm/github-markdown-css"
                           "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/github.min.css")
      markdown-xhtml-header-content "
<style><!-- CSS HERE --></style>
<script src=\"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js\"></script>
<script>hljs.initHighlightingOnLoad();</script>"
      markdown-xhtml-body-preamble "<div class=\"markdown-body\">"
      markdown-xhtml-body-epilogue "</div>")
```

`<!-- CSS HERE -->` には自分の好きなCSSを書いてください。ちなみに、私の設定はこちら。

```css
body {
  padding: 1rem 3rem;
}

@media only screen {
  body {
    border: 1px solid #ddd;
    margin: 1rem auto;
    max-width: 45rem;
    padding: 3rem;
  }
}

.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6,
.markdown-body h6,
.markdown-body strong {
  font-weight: 700;
}
```

# 動機

Emacsでmarkdownを書くときは、**markdown-mode**を常用している。

*Markdown Mode for Emacs* - https://jblevins.org/projects/markdown-mode/

markdown-modeでは任意のmarkdownパーサが指定できる。インストールガイドには**MultiMarkdown**が例として挙げられている。

```el
(setq markdown-command "multimarkdown")
```

*MultiMarkdown* - http://fletcherpenney.net/multimarkdown/

MultiMarkdownでは「取り消し(strikethrough)」構文がデフォルトでサポートされていなかったので、いい機会にGitHubでの表示に近いパーサや設定を探してみることにした。

[Can you add support strikethrough text? · Issue #130 · fletcher/MultiMarkdown-4](https://github.com/fletcher/MultiMarkdown-4/issues/130)

プッシュ前にGitHubでどのように表示されるかを確認することが多々あるので、できればGitHubが使っているパーサが望ましいという事情があった。
(プッシュ後に想定外の表示がされると、かなりヘコむ :tired_face: )

＊ちなみに、markdown-modeでは<kbd>C-c C-c p</kbd>でHTMLプレビューがブラウザで表示される。

# 調査

## GitHub Markup

ググると**GitHub Markup**なるものを見つけた。

*GitHub Markup* - https://github.com/github/markup

README抜粋。

> This library is the first step of a journey that every markup file in a repository goes on before it is rendered on GitHub.com:

> *このライブラリは、GitHub.comでレンダリングされる前に、リポジトリ内のすべてのマークアップファイルが実行される最初のステップです。*

「マークアップ」とあるのは、markdownだけではなくtextileやrdocなど、様々な種類のマークアップをGitHubがサポートしているからだろう。

```sh
gem install github-markup
```

とあるので、Rubyで書かれていると思い、ソースをのぞいてみた（Rubyは仕事で使っている）。
ソースファイルは10個くらいで少ない。各マークアップパーサのラッパーライブラリのようだ。

https://github.com/github/markup/blob/9fe0536/lib/github/markup/markdown.rb

以下、抜粋。

```rb
MARKDOWN_GEMS = {
  "commonmarker" => proc { |content|
    CommonMarker.render_html(content, :GITHUB_PRE_LANG, [:tagfilter, :autolink, :table, :strikethrough])
  },
  "github/markdown" => proc { |content|
    GitHub::Markdown.render(content)
  },
  "redcarpet" => proc { |content|
    Redcarpet::Markdown.new(Redcarpet::Render::HTML).render(content)
  },
  "rdiscount" => proc { |content|
    RDiscount.new(content).to_html
  },
  "maruku" => proc { |content|
    Maruku.new(content).to_html
  },
  "kramdown" => proc { |content|
    Kramdown::Document.new(content).to_html
  },
  "bluecloth" => proc { |content|
    BlueCloth.new(content).to_html
  },
}

# ...(省略)...

def load
  return if @renderer
  MARKDOWN_GEMS.each do |gem_name, renderer|
    if try_require(gem_name)
      @renderer = renderer
      return
    end
  end
  raise LoadError, "no suitable markdown gem found"
end
```

`MARKDOWN_GEMS`定数には色々なmarkdownパーサが並べてある。
`load`メソッドを見ると、`MARKDOWN_GEMS`を上から順に読み込んでいき、`require`に成功したらそのパーサを採用している。

一番上は`commonmarker`だ（`github/markdown`ではない）。たしかにREADMEにも`commonmarker`が記載してあった。

> - .markdown, .mdown, .mkdn, .md -- `gem install commonmarker` (https://github.com/gjtorikian/commonmarker)

ということで、CommonMarkerを調べてみる。

## CommonMarker

https://github.com/gjtorikian/commonmarker

README抜粋。

> Ruby wrapper for libcmark-gfm, GitHub's fork of the reference parser for CommonMark. It passes all of the C tests, and is therefore spec-complete. It also includes extensions to the CommonMark spec as documented in the GitHub Flavored Markdown spec, such as support for tables, strikethroughs, and autolinking.

> *libcmark-gfmのRubyラッパー、CommonMarkのリファレンスパーサのGitHub版フォーク。C言語の全テストをパスしているため、仕様を完全に満たしています。 また、テーブル、取り消し線、自動リンクのサポートといった、GitHub Flavored Markdown仕様ドキュメントにあるCommonMark仕様拡張も含まれています。*

**CommonMark**というのは、Markdownの標準仕様のようだ（初めて知った）。

http://commonmark.org/

日本語だと、以下の記事が詳しい。

- [CommonMarkの現状 2017年4月版 - Islands in the byte stream](http://gfx.hatenablog.com/entry/2017/04/12/103117)
- [CommonMarkの仕様とか - Qiita](https://qiita.com/Prof-Cheese/items/9629438b06aacc068c98)

# 設定

ということで、`github-markup`と`commonmarker`をインストールする。

```sh
gem install github-markup commonmarker
```

`github-markup`コマンドがインストールされたので、`markdown-mode`に設定する。

```el
(setq markdown-command "github-markup")
```

いざ、<kbd>C-c C-c p</kbd>でHTMLプレビューしてみると、ブラウザに次のメッセージが表示された。

![スクリーンショット 2017-11-24 1.17.51.png](https://qiita-image-store.s3.amazonaws.com/0/104952/545651e9-d278-ff4a-7894-544d082b434c.png)


`github-markup`コマンドは標準入力を受け付けないようなので、次の設定を加える。

```el
(setq markdown-command-needs-filename t)
```

すると、無事表示された。

![スクリーンショット 2017-11-24 1.19.14.png](https://qiita-image-store.s3.amazonaws.com/0/104952/2575d8f3-1d94-d8c1-c217-5366a590bed4.png)

# シンタックスハイライト

これで終わりかと思いきや、コードスニッペトのシンタックスハイライトがうまく効いてないことに気づいた。
シンタックスハイライトには**highlight.js**を使っているのだが、言語の選択がおかしいところがある。
（GitHubのハイライトがどうなってるか調べてみたが、分からなかった。少なくともhighlight.jsは使ってないようだった。）

*highlight.js* - https://highlightjs.org/

![スクリーンショット 2017-11-24 1.28.17.png](https://qiita-image-store.s3.amazonaws.com/0/104952/53124856-053b-e535-9b6d-777db8561ea2.png)


どうも、highlight.jsが付与する`class`属性に正しく反映されていないようだ。
`<pre>`の`lang`属性とは？もう一度GitHub Markupのソースコードを確認する。

https://github.com/github/markup/blob/9fe0536/lib/github/markup/markdown.rb#L8

```rb
CommonMarker.render_html(content, :GITHUB_PRE_LANG, [:tagfilter, :autolink, :table, :strikethrough])
```

`GITHUB_PRE_LANG`の説明を、CommonMarkerのREADMEに見つけた。

https://github.com/gjtorikian/commonmarker/blob/adf83c5/README.md#render-options

> `:GITHUB_PRE_LANG` Use GitHub-style `<pre lang>` for fenced code blocks.

`<pre lang>`はCommonMarkerが出力していることがわかった。

`<pre lang>`というHTMLマークアップにhighlight.jsが対応してないっぽいので、highlight.jsのドキュメントにあたった。

https://highlightjs.org/usage

ドキュメントには`<code class="html">`のように言語を明示的に指定する方法があったので、JavaScriptでDOMを書き換えるようにしてみた（highlight.jsにはシンタックスの自動検知機能がある）。

```html
<pre lang="el"><code>...</code></pre>
```

↓ (JavaScriptでDOM書き換え)

```html
<pre lang="el"><code class="el">...</code></pre>
```

markdown-modeの設定はこんな感じ。この`markdown-xhtml-header-content`の値が`<head>`に挿入される。

```el
(setq markdown-xhtml-header-content "
<script src='http://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js'></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
  document.body.classList.add('markdown-body');
  document.querySelectorAll('pre[lang] > code').forEach((code) => {
    code.classList.add(code.parentElement.lang);
    hljs.highlightBlock(code);
  });
});
</script>
")
```

DOM書き換えのコードはこれ。`lang`属性の値を`<code>`の`class`属性に追加している。

```js
document.querySelectorAll('pre[lang] > code').forEach((code) => {
  code.classList.add(code.parentElement.lang);
  hljs.highlightBlock(code);
});
```

プレビューを表示してみると、うまくいった。

![スクリーンショット 2017-11-24 1.54.24.png](https://qiita-image-store.s3.amazonaws.com/0/104952/4d040ba9-9a77-5e2c-b13e-ce60b8663613.png)

ただ、残念なことにhighlight.jsはEmacs Lispのシンタックスに対応してなかった :cry:（Lispには対応している）。
GitHubやQiitaは対応しているので、彼らは独自のシンタックスハイライターを実装しているのかもしれない。

# まとめ

最終的な設定はこうなった（CSSには[github-markdown-css](https://github.com/sindresorhus/github-markdown-css)を利用）。

https://github.com/ybiquitous/dotfiles/blob/c81417b/HOME/.emacs.d/inits/07_markdown.el

```el
(use-package markdown-mode
  :commands (markdown-mode gfm-mode)
  :mode (("\\.md\\'" . gfm-mode)
          ("\\.markdown\\'" . gfm-mode))
  :config
  (add-hook 'markdown-mode-hook #'flyspell-mode)
  (setq
    markdown-command "github-markup"
    markdown-command-needs-filename t
    markdown-content-type "application/xhtml+xml"
    markdown-css-paths '("https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.min.css"
                          "http://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/styles/github.min.css")
    markdown-xhtml-header-content "
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<style>
body {
  box-sizing: border-box;
  max-width: 740px;
  width: 100%;
  margin: 40px auto;
  padding: 0 10px;
}
</style>
<script src='http://cdn.jsdelivr.net/gh/highlightjs/cdn-release/build/highlight.min.js'></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
  document.body.classList.add('markdown-body');
  document.querySelectorAll('pre[lang] > code').forEach((code) => {
    code.classList.add(code.parentElement.lang);
    hljs.highlightBlock(code);
  });
});
</script>
"))
```

最初は簡単にできるかなと考えていたが、色んな箇所でつまづいて、3時間くらいかかった（記事執筆時間は含まず）。
特に、最後のシンタックスハイライトでつまづいたのが痛かった :disappointed_relieved:。
もっと時間をかけて調べればいい解決方法が見つかるかもしれないが、休日だったこともあり、心が折れた（GitHubが使っているハイライター、OSS化されてないかな…）。
今後もし解決方法が見つかれば、この記事を更新するつもり。

時間がかかった割には成果は少ないが、CommonMarkとか新たな知見を得られたので、良しとする :blush: 。