CommonMark リストの仕様を理解したい

# CommonMark リストの仕様を理解したい

CommonMark におけるリストとリスト項目の仕様を理解したい。

仕様の該当箇所は以下を参照。

> - [§5.2 List items \- CommonMark Spec](https://spec.commonmark.org/0.30/#list-items)
> - [§5.3 Lists \- CommonMark Spec](https://spec.commonmark.org/0.30/#lists)

$
\newcommand{\mathsymb}[1]{\mathtt{#1}}
\newcommand{\w}{\mathsymb{W}}
\newcommand{\n}{\mathsymb{N}}
\newcommand{\nn}{\nu}
$

## ■ 言葉の定義

本記事で利用する言葉をいくつか定義しておきたい。

- **リスト**

    番号なしリストと番号付きリストを指す。
    今回、チェックリストは除外するものとする。

- **番号なしリスト (Unordered list)**

    リストの先頭にビュレット (`●`) が付与されるリスト。箇条書きとも言われる。

- **番号付きリスト (Ordered list)**

    リストの先頭に番号が付与されるリスト。
    リストマーカーに `1.` や `1)` が利用され、数字は 1 以上かつ 4 桁以下の整数を利用する。すべて同じ数字であっても 1 づつ繰り上がる

    リストの通し番号を任意の数字に指定したい場合は、先頭の数字を指定する。後ろのリストはその番号に続く。また、リストの通し番号を 0 から始めたい場合には `0.` を使うことも出来る。

- **リストマーカー**

    リストを記述するため行頭に記す文字。`-`、`+`、`*` や `1`、`1)` が使われる。

    リストマーカーの文字数を**リストマーカー幅** ($\w$) と呼ぶ。それぞれについては以下のようになる。

    - `-`、`+`、`*`：$\w=1$
    - `1.`、`1)`：$\w=2$

- **リスト項目**

    リストにされている各項目。

    ```markdown
    リストマーカー
    ↓
    ~
    - ここは “リスト項目”

    1. ここは “リスト項目”
    ~~
    ↑
    リストマーカー
    ```

- **スペース**

    半角スペースを指す。

- **インデント**

    行頭に半角スペースを挿入すること。
    このインデントに使用された半角スペースの数を**インデント量**と呼ぶこととする。

- **項目インデント**

    リスト項目をリストマーカー左端から半角スペースを挿入すること。リストでは、少なくとも 1 つの半角スペースを挿入必要がある。

    ```markdown
    リストマーカー
    ↓
    - ここはリスト項目
     ~
     ↑
     項目インデント
    ```

    このインデントに使用された半角スペースの数を**項目インデント量** ($\n: 1 \leq \n \leq 4$) と呼ぶこととする。この項目インデント量は表示結果に影響しない。

    ただし、$\n \geq 5$ の場合、リスト項目は Indented code block として解釈される。

- **ルーズ (loose)** と**タイト (tight)**

    リスト内に空行が 1 つ以上ある場合、リスト全体はルーズ（間隔の空いたゆるいリスト）になる。
    逆に、リスト内に空行がない場合、リスト全体はタイトとなる。

- **Lazy 性 (laziness)**

    前行のブロックの構造を引き継ぐ概念。
    `>` を利用したブロッククォートでよく見られる。

    ```markdown
    > ここはブロック引用。
    ここもブロッククォートとなる。
    これが Lazy 性。
    ```

    リストではリスト項目に適応される。

    このように前行の構造を引き継いだ行を “**Lazy continuation line**（遅延継続行<sup>[要出典]</sup>）” と呼ぶ。Lazy continuation line ではインデントが無視される。ハードラップする際に有用。Lazy 性の詳細は以下の記事を参照。

    > [Markdown の laziness とは - Qiita](https://qiita.com/tk0miya/items/5f3fdb16b7b07f8250d1)

- **改行**

    エディタ上で行を改めること。

- **ハードラップ**

    エディタ上で 1 つの文章を改行によって複数の行に分割すること。ハードラップされた文章内に空行は含まれない。

- **強制改行**

    ハードラップを改行として理解するパーサの設定や `<br>` タグによって、1 つのパラグラフが複数の行に渡って表示されるようになること。強制改行された文章は、通常同じブロックとして認識される。

## ■ 段落を中断するリスト

段落を中断する (Interrupt) ようにリストを挿入する場合は、リストの前行に空白行を挟まない。ただし、番号付きリストは必ず `1.` から開始される必要がある。

```markdown
次のリスト：
- 玉ねぎ
- 人参
- じゃがいも

は番号なしリストです。
```

次のリスト：
- 玉ねぎ
- 人参
- じゃがいも

は番号なしリストです。

```markdown
次のリスト：
1. 玉ねぎ
1. 人参
1. じゃがいも

は番号付きリストです。
```

次のリスト：
1. 玉ねぎ
1. 人参
1. じゃがいも

は番号付きリストです。

番号付きリストが `1.` から開始される必要があるのは、`1.` 以外ではリテラルに解釈されるためである。したがって、次のような書き方をするとリストとして解釈されません。

```markdown
番号付きリスト：
11. 玉ねぎ
11. 人参
11. じゃがいも

はリストに解釈されません。
```

番号付きリスト：
11. 玉ねぎ
11. 人参
11. じゃがいも

はリストに解釈されません。

## ■ ルーズなリストとタイトなリスト

リストにはルーズとタイトがある。これはリスト内に空行が 1 つ以上あった場合はルーズ、ない場合はタイトとなる。

ルーズなリストの例：

```markdown
- 玉ねぎ

- 人参

- じゃがいも
```

- 玉ねぎ

- 人参

- じゃがいも

タイトなリストの例：

```markdown
- 玉ねぎ
- 人参
- じゃがいも
```

- 玉ねぎ
- 人参
- じゃがいも

このため、タイトなリストが複数連続する場合、ルーズなリストとして認識される。これを避けるには [\# 次節](#-段落を中断するリスト) を参照。

また、このルーズとタイトはブロックごとに計算される。以下の例では、野菜のリストはルーズとなっているが、生産量ランキングのリストはタイトとなっている。

```markdown
- 玉ねぎ

  1. 北海道
  1. 佐賀県
  1. 兵庫県

- 人参

  1. 北海道
  1. 千葉県
  1. 徳島県

- じゃがいも

  1. 北海道
  1. 長崎県
  1. 鹿児島県
```

- 玉ねぎ

  1. 北海道
  1. 佐賀県
  1. 兵庫県

- 人参

  1. 北海道
  1. 千葉県
  1. 徳島県

- じゃがいも

  1. 北海道
  1. 長崎県
  1. 鹿児島県

## ■ 連続するリストの分割

複数のリストを連続して記述したいとき、以下のように記述すると 1 つのルーズなリストとして解釈される。

```markdown
- 玉ねぎ
- 人参
- じゃがいも

- いちご
- もも
- グレープフルーツ
```

- 玉ねぎ
- 人参
- じゃがいも

- いちご
- もも
- グレープフルーツ

2 つのタイトなリストとして解釈させるには、間にコメントアウト (`<!-- -->`) を挿入すると良い。

```markdown
- 玉ねぎ
- 人参
- じゃがいも

<!--  -->

- いちご
- もも
- グレープフルーツ
```

- 玉ねぎ
- 人参
- じゃがいも

<!--  -->

- いちご
- もも
- グレープフルーツ

コメントアウトを挿入する他に、リンクの参照スタイルの参照先 (`[ref]: link`) を置いても同じ結果を得る。

あるいは、リストマーカーを変える。

```markdown
- 玉ねぎ
- 人参
- じゃがいも

* いちご
* もも
* グレープフルーツ
```

- 玉ねぎ
- 人参
- じゃがいも

* いちご
* もも
* グレープフルーツ

番号付きリストでは、`1.` と `1)` を使い分ければ良い。

## ■ リスト項目のハードラップ

:::note info
Qiita では 1 回の改行が強制改行として理解されるため、リスト項目が複数行に渡って表示されます。この複数行に渡る文章が 1 つのリスト項目です。
:::

リスト項目が長いためにハードラップ（折り返し）したい場合、ハードラップされた 2 行目以降の行は、リストマーカー幅 $\w$ と項目インデント量 $\n$ の合算量でインデントすれば良い。すなわち、リスト項目の行の頭と同じ位置までインデントする必要がある。

$\w=1$ で $\n=2$ の場合、2 行目以降は $\w+\n=3$ のインデント量があれば良い。

```markdown
-  玉ねぎ
   （愛知県産）
-  人参
   （千葉県産）
-  じゃがいも
   （鹿児島県産）
```

-  玉ねぎ
   （愛知県産）
-  人参
   （千葉県産）
-  じゃがいも
   （鹿児島県産）

ただし、これはリスト項目の Lazy 性から 2 行目以降を Lazy continuation line と捉えることで、インデント量が $\w+\n$ でない場合であっても問題がない。したがって、次のように表現しても同じ結果を得る。

```markdown
-  玉ねぎ
（愛知県産）
-  人参
（千葉県産）
-  じゃがいも
（鹿児島県産）
```

-  玉ねぎ
（愛知県産）
-  人参
（千葉県産）
-  じゃがいも
（鹿児島県産）

このインデント量 $\w+\n$ が重要になるシーンはブロックをリスト内にネストするときである。[\# 次節](#-リスト内にネストされるブロック) を参照してほしい。

## ■ リストにブロックを挿入する

リスト内にブロックをネストする場合には、タイトとルーズを制限される場合がある。少ない例だが、以下に表とした。

|      ブロック       | タイト | ルーズ |
| :-----------------: | :----: | :----: |
|     パラグラフ      |  不可  |   ✓   |
|       リスト        |   ✓   |   ✓   |
|    ブロック引用     |   ✓   |   ✓   |
| Indented code block |  不可  |   ✓   |
|  Fenced code block  |   ✓   |   ✓   |
|      表 (GFM)       |   ✓   |   ✓   |

パラグラフと Indented code block をタイトなリストにネストした場合、Lazy 性からハードラップされたリスト項目として認識される。

### ▽ ブロックのネスト化

ブロックをネストするには、$\w+\n$ でインデントすることでネストすることが出来る。

以下の例では $\w=1$、$\n=1$ であり、$\w+\n=2$ のインデント量があれば良い。

````markdown
- リスト
  1. 玉ねぎ
  1. 人参
  1. じゃがいも
- ブロック引用
  > 石炭をば早や積み果てつ。
- Fenced code block
  ```python
  print("Hello World")
  ```
````

- リスト
  1. 玉ねぎ
  1. 人参
  1. じゃがいも
- ブロック引用
  > 石炭をば早や積み果てつ。
- Fenced code block
  ```python
  print("Hello World")
  ```

あるいは、ルーズにする場合も同様に $\w+\n$ でネストすることが出来る。

以下の例では $\w=1$、$\n=1$ であり、$\w+\n=2$ のインデント量があれば良い。

````markdown
- パラグラフ

  人間失格

- リスト

  1. 玉ねぎ
  1. 人参
  1. じゃがいも

- ブロック引用

  > 私は、その男の写真を三葉、見たことがある。

- Indented code block

      print("Hello World")

- Fenced code block

  ```python
  print("Hello World")
  ```
````

- パラグラフ

  人間失格

- リスト

  1. 玉ねぎ
  1. 人参
  1. じゃがいも

- ブロック引用

  > 私は、その男の写真を三葉、見たことがある。

- Indented code block

      print("Hello World")

- Fenced code block

  ```python
  print("Hello World")
  ```

### ▽ ブロックのリスト項目化

リスト項目をブロックにする場合、以下のように表現することが出来る。1 行を超える場合は、$\w+\n$ のインデントを行う。

以下の例では $\w=1$、$\n=1$ であり、$\w+\n=2$ のインデント量があれば良い。Indented code block は ``- ``&nbsp;に続いて 4 つのスペースが必要になる。

````markdown
- > 親譲りの無鉄砲で子供の時から損ばかりしている。
- 1. 玉ねぎ
  1. 人参
  1. じゃがいも
-     print("Hello World")
- ```python
  print("Hello World")
  ```
````

- > 親譲りの無鉄砲で子供の時から損ばかりしている。
- 1. 玉ねぎ
  1. 人参
  1. じゃがいも
-     print("Hello World")
- ```python
  print("Hello World")
  ```

あるいは、リストマーカーの次の行に $\w+\n$ のインデントを行うことでも、リスト項目をブロックにすることが出来る。

以下の例では、$\w=1$、$\n$ は `-` の後の改行 1 つがカウントされているため $\n=1$。（ただし、2 回以上の改行はリストとして理解されなくなる）

````markdown
-
  芥川龍之介
-
  > ある日の暮れ方のことである。
-
  1. 玉ねぎ
  1. 人参
  1. じゃがいも
-
      print("Hello World")
-
  ```python
  print("Hello World")
  ```
````

-
  芥川龍之介
-
  > ある日の暮れ方のことである。
-
  1. 玉ねぎ
  1. 人参
  1. じゃがいも
-
      print("Hello World")
-
  ```python
  print("Hello World")
  ```

複数行に渡るブロックは Lazy continuation line とはならないため、必ずインデントが必要になる。

## ■ The four-space rule

*“The four-space rule”* とは、インデントを行う際に、4 つのスペースを 1 つの塊として扱うルールを指す。これは Markdown を開発した John Gruber によって提唱されたルールである。

このルールの元では $\w+\n$ のインデント量でインデントせず、常に 4 つのスペースを使ってインデントすることでハードラップやネストを表現できる。

実際のところ、CommonMark によるハードラップやネストのためのインデント量 $\nn$ はおおよそ $2 \leq \nn \leq 6$（$\w=1\text{ or }2$、$1 \leq \n \leq 4$）であり、The four-space rule に従った書き方をしたとしても、これまでの仕様を反することはほとんどない。また、項目インデント量を $\n=1$ とする場合が多いため、$\w+\n=2 \text{ or } 3$ を取ると思えば、The four-space rule によってリストにネストされているブロックは正しく理解される。

<!-- $\nn \geq \w+\n+4$ とならない限りには問題ない。 -->

### ▽ 注意点

$\nn = 5 \text{ or } 6$ であった場合、The four-space rule に従っていると上手くレンダリングされないため注意が必要になる。

```markdown
-    W=1、N=4 のリストにブロック引用を The four-space rule の元でネストしようとする

    > ネストされずにリストの外で Indented code block となる。
```

-    $\w=1$、$\n=4$ のリストにブロック引用を The four-space rule の元でネストしようとする

    > ネストされずにリストの外で Indented code block となる。

また、Indented code block はリスト項目を $\n=3$ で項目インデントする必要がある。これは、結果的に The four-space rule に沿うような形にしている。（この仕様で意図したレンダリングにならないパーサがたまにあるが……）

```markdown
-   Indented code block

        print("Hello World")

1.  Indented code block

        print("Hello World")
```

-   Indented code block

        print("Hello World")

1.  Indented code block

        print("Hello World")

しかしながら、Indented code block よりも Fenced code block の方がハイライトを付与できて実用性が高いため、Indented code block を使わなければ問題ないだろう。

## ■ HTML タグを利用した改行

`<br>` タグを利用してリスト項目を強制改行することが出来る。

```markdown
- リスト項目を<br> ここで強制改行
- リスト項目を<br> ここで強制改行
```

- リスト項目を<br> ここで強制改行
- リスト項目を<br> ここで強制改行

Qiita では、1 回の改行が強制改行としてして理解されるため、Lazy continuation line を強制改行後として見せることが出来る。

```markdown
- リスト項目は
  ここで改行される
- リスト項目は
  ここで改行される
```

- リスト項目は
  ここで改行される
- リスト項目は
  ここで改行される

# 参考

- [§5.2 List items \- CommonMark Spec](https://spec.commonmark.org/0.30/#list-items)
- [§5.3 Lists \- CommonMark Spec](https://spec.commonmark.org/0.30/#lists)

# 余談

リストの仕様がややこしいすぎる。

元祖 Markdown が仕様と異なる挙動を示したおかげで (?) さまざまなインデント量を認める必要が出るようになってしまったようだ。実際、ハードラップやネストに必要なインデント量 $\nn$ は $(\w+\n) \leq \nn < (\w+\n+4)$ の範囲内であれば、意図通りに解釈される。また、ハードラップに関しては Lazy 性を考慮することで、$0 \leq \nn < (\w+\n+4)$ の範囲内で意図通りに解釈される。（$\nn \geq (\w+\n+4)$ の場合、Indented code block として理解される）