30 December, 2025
Issue
Problems in v1 spec
Problem 1: how to replace indent segments
- paragraph 1
-- sub list item 1
-- sub list item 2
paragraph 2
- list item 2
How to make "paragraph 2" belong to first list item?
Problem 2: strong carryover tag in mid of list
- list item 1
#some-macro
-- sub list item 1
-- sub list item 2
- list item 2
How to make #some-macro
applied to sub list without breakin the whole list?
Let's write down the targeted AST for both cases.
(list
(list-item
(list
(list-item)
(list-item))
(paragraph))
(list-item))
(list
(list-item
(carryover-tag)
(list
(list-item)
(list-item)))
(list-item))
to generalize, we are looking for a syntax that is capable to represant this AST.
(list-item
(some-block)
(some-block))
some-block
here can be one of:
- paragraph
- list (or list item, more on that below)
- tags
v1 spec did have this syntax with indent segments, but no one liked it. So yeah, I am basically going to redefine nestable detached modifiers from scratch.
Other syntaxs
In HTML, nesting is most simple and basic concept.
<li>
<p>list content 1</p>
<p>list content 2</p>
</li>
HTML nests Abstract Objects by defining a tag with opening/closing tags.
But Norg isn't similar to HTML. Let's see same example with pug.
li
p list content 1
p list content 2
In pug, whitespace indentation represents the nested level and first token in each lines represent the kind of the block.
Compared to pug, in norg, detached modifier prefix can represent level and kind of the block at same time. Prefix character represents the kind and repeated count of prefix represents the level.
*** level 3 heading
-- level 2 unordered list item
~~~~~ level 5 ordered list item
>>> level 3 quote
So why v1 implementation didn't work well?
By reading list item representation from other markup languages, you might notice that a list actually has two types of lists:
- list of list items
- list of list item contents (=blocks)
In other words, list are nested by default because a list item can have list of blocks as content.
v1 spec didn't work well because it required special syntax; indent segments to change list item content from a block to list of blocks.
Solution
Expanding Norg's detached modifier syntax, if we define prefix for a block which can be a paragraph or a tag, we can represent the list item above like this:
-
// list content 1
// list content 2
I'm using detached modifier in similar form suggested from #50
Because this prefix don't have any types, it won't be visible in AST. So the parsed result would be like this:
(list
(list-item
(paragraph)
(paragraph)))
But users won't want to write /
prefix for each list item contents even when list item has only one child as a content. It looks relatively fine in pug, but not in Norg.
ul
li
p list item 1
li
p list item 2
-
// list item 1
-
// list item 2
So it will be better if we allow merging first sublist item content in to list item:
- list item 1
- list item 2
We don't need //
for first list item content because norg can know the kind and level by context. (It is paragraph because it's normal text without prefix, and the nest level should be at least 2 because it is placed under level 1 list item)
This merging should be optional because there are some cases when user needs to put attributes to a paragraph instead of list item.
ul(color="red")
li(color="green")
p(color="blue") paragraph 1
p(color="yellow") paragraph 1
+color red
- (color green)
// paragraph 1
// paragraph 2
Tables
A table consists of three types of lists.
- list of rows
- list of cells
- list of cell contents (=blocks)
So basically table is a list with level 2 sublist. Now we can represent a table below with nestable detached modifier syntax.
+----+--------------------------+
| A1 | B1 |
+----+----+---------------------+
| A2 | B2 | C2 |
| +----+---------------------+
| | B3 | C3 with |
| | | |
| | | multiple paragraphs |
+----+----+---------------------+
= (thead) tr(thead)
== td
/// A1 p A1
== (colspan 2) td(colspan="2")
/// B2 p B1
= tr
== (rowspan 2) td(rowspan="2")
/// A2 p A2
== td
/// B2 p B2
== td
/// C2 p C2
= tr
== td
/// B3 p B3
== td
/// C3 with p C3 with
/// multiple paragraphs p multiple paragraphs
- I placed pug's representation of same table to make norg code easier to understand.
- I'm using
=
prefix instead of original:
. I'll address this below- So
=
represents a row and==
represents a table cell
Let's ignore C3, say we can omit merge level 3 ///
prefix just like we did for list items.
= (thead)
== A1
== (colspan 2) B1
=
== (rowspan 2) A2
== B2
== C2
=
== B3
== C3
To represent C3 cell, we can use |group
tag which can wrap the chunk of norg texts into single block that can be placed on detached modifier.
= (thead)
== A1
== (colspan 2) B1
=
== (rowspan 2) A2
== B2
== C2
=
== B3
== |
C3 with
multiple paragraphs
|
Now another problem occurs: there are 3 vertical list coexist in this text (list of rows, list of cells, list of blocks.) Cells in a row is not a vertical list, not horizontal list.
= (thead) = A1 = B1
table is one kind of detached modifier which can
* heading * <- what if this was also empty heading
=
== A1 = B1
should be same to
=
== A1
== B1
-
// content
-- content
- content
-- content
- - content 1
-- content 2
- - content 1 - content 2
* dummy heading
** foofoofoo ****** barbarbar
^^^^^^^^^ ^^^^^^^^^
| |
| `--- It's "title" (`*`) with level 3
| (repeated count doesn't matter)
|
`--- It's level 2 "title"
content under level 2 heading (not level 2! because there is no level 3 heading here)
(heading
title: (title
(paragraph) ; "foofoofoo"
(title
(paragraph))) ; "barbarbar"
...)
* **** fo *********** ba
(heading
title: (title ; <empty title>
(title
(paragraph) ; "fo"
(title
(paragraph))) ; "ba"
title is a structural detached modifier, heading matches to *unordered list* from nestable detached modifiers.
,--- A1 is nested table item because it is a table item (paragraph with special
| prefix) under level 1 table item
|
= ==== A1 =========== B1
== C1 ^^ ^^
^^ | |
| | `--- It's level 2 table item
| | (repeated count doesn't matter)
| |
| `--- It's level 2 table item
| (repeated count doesn't matter)
|
`--- It's level 2 table item (obviously)
(table-item
(table-item
(paragraph)) ; "A1"
(table-item
(paragraph)) ; "B1"
(table-item
(paragraph))) ; "C1"
+caption Detached Modifiers
= = Name = Kind = List direction
=====================================================
= = title = structural = horizontal
= = list item = nestable = vertical
= = table item = table = horizontal / vertical
= A1
== B1
== B2 <- what about this?
=== C1
==== D1
+---+-----------+
|A1 |B1 |
| +---+---+---+
| |B2 |C2 |D1 |
| | +---+ |
| | |C3 | |
+---+---+---+---+