Note
30 December, 2025
Uscases
\gh[neovim/neovim] -> {https://github.com/neovim/neovim}[neovim/neovim]
\nvimhelp[:h 'shiftwidth'] -> {https://neovim.io/doc/user/options.html#'shiftwidth'}[`:h 'shiftwidth'`]
\latex[\alpha + \beta = \gamma] -> `\\alpha + \\beta = \\gamma`(math)
Summary
Remove delimiting modifiers
* Heading level 1
** Heading level 2
*** Heading level 3
**** Heading level 4
---
---
---
Text under first level heading
===
Text in root level of document
User need to use multiple weak delimiting modifiers to close a middle level heading; heading with level that is lower than root but higher than current.
Solution: Replace both delimiting modifiers with empty heading.
* Heading level 1
** Heading level 2
*** Heading level 3
**** Heading level 4
**
Text under first level heading
*
Text in root level of document
Benefits:
- can be explicit about which level of heading it will close.
- doing a job of two syntax elements (weak delimiting modifier & strong delimiting modifier), resulting less special punctuation and easier to parse
- can close middle level heading easily
- prevent closing heading while typeing level 3+ list item prefix. this was annoying
(WIP) New type of structural detached modifiers: details
<details>
tag in HTML consists of a summary and its contents.
So a title and a range. Details can be represented with structural detailed modifiers.
< summary of detail
content of detail
using prefix without following pararaph to close the range. (just like headings)
<
Basically same syntax with different prefix character. But it has lower nesting level than headings. Useful when heading with lowest level is required (e.g. details tag)
* Heading level 1
** Heading level 2
*** Heading level 3
**** Heading level 4
< summary of details
content of details
<< nested details
content of nested details
<
It is still possible to define carryover tag #details
to change heading to details, but then user should make sure it has lower level then current heading.
heading
TODO:
- make table syntax proposal introducing horizontal way to position items
- justify null detached modifier
Headings and Lists are different
Headings; structural detached modifiers are, as their name suggests, ways to structure whole document. Even after closing level-1 heading, the following content can be defined as content of level-0 heading (root level of the document.) But list items and table rows are different. They gather and construct an arbirary object called "list" and "table". When a list or table is closed, we have to go back to context of outside world, normal document area where we can write root level contents.
structural detached modifiers:
- heading: with blocks below, it creates an AO called "section"
- list item: with multiple list item, they create and AO called "list"
- table row: with multiple table rows, they create and AO called "table"
multi line heading (heading close modifier)
This is a Heading
yes, in multiple lines
======================
paragraph
## This is a Heading in single line
paragraph
Think about this syntax in CommonMark which exists for some usecases where user needs multi line headings.
Multi line heading has at least one line of spacer with multiple =
characters they cannot be used without that spacer line.
Single line heading can be used without spacer line.
** This is a Heading
yes, in multiple lines
paragraph
** This is a Heading in single line *
paragraph
We can have similar syntax by specifying heading title as a paragraph and introducing heading close modifier.
Now you can use *\n
to explicitly end the heading title.
This allows both heading and paragraph divided by line and heading and paragraph in compact form (no spacer line between) in same grammar base.
Reimplemented Indent Segment
Yes, I'm the one who actively removed it from spec. But having indent segment now feels reasonable.
tldr; Redefine it as |group...|end
replacement from v1 spec.
- |
paragraph
paragraph
|
This also makes sense with vhyrro's table analogy about nestable detached modifiers. Think about it:
- list item in vertical direction
with unnecessary (maybe not even exist in spec) closing modifier -
- |
list item *content*
in horizontal direction
with closing modifier
|
Makes sense, doesn't it?
Table implementation
Now we can even implement table with this paradigm:
rethink about lists
list has two types of lists:
- list of list items
- list of list item contents
so correct form of list item should look like this:
- // list content 1 // list content 2
Think about this example in HTML:
<li> <p>list content 1</p> <p>list content 2</p> </li>
Imagine a HTML-based syntax that uses indentation instead of closing tags ... so yeah, lets write that in pug.
li p list content 1 p list content 2
Norg's detached modifier prefix can represent kind and level in same time (prefix character repersents the kind and repeated prefix represents the level.) So if we define
/
as paragraph detached modifier, example above will be represented in norg like this:- // list content 1 // list content 2
But users won't want to write pargraph prefix for each list items even when list item contains single pargraph. 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 list item content in to list item:
- list item 1 - list item 2
We don't need
//
here because norg can know the kind and level of each paragraphs by context. (It is a paragraph because it's normal text without prefix, and the nest level would 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.
+color red - (color green) // (color blue) paragraph 1 // (color pink) paragraph 2
+----+--------------------------+
| A1 | B1 |
+----+----+---------------------+
| A2 | B2 | C2 |
| +----+---------------------+
| | B3 | C3 with |
| | | multiple paragraphs |
+----+----+---------------------+
table detached modifier's level decide which list it is representing
level 1: rows level 2: columns level 3: cell contents
and ranged cell is done by attributes
tr(thead)
td
p A1
td(colspan="2")
p B1
tr
td(rowspan="2")
p A2
td
p B2
td
p C2
tr
td
p B3
td
p C3 with
p multiple paragraphs
I'm using
=
instead of:
to represent the direction similar to nestable detached modifiers
= (thead)
==
=== A1
== (colspan 2)
=== B1
=
== (rowspan 2)
=== A2
==
=== B2
==
=== C2
=
==
=== B3
==
=== C3 with
=== multiple paragraphs
Lets ignore C3, say we can omit level 3 prefix:
= (thead)
== A1
== (colspan 2) B1
=
== (rowspan 2) A2
== B2
== C2
=
== B3
== C3
To represent C3 cell easily, lets bring back the indent segments from v1 spec to represent range of a cell.
= (thead)
== A1
== (colspan 2) B1
=
== (rowspan 2) A2
== B2
== C2
=
== B3
== |
C3 with
multiple paragraphs
|
Now here is a problem: there are 3 vertical list coexist in this syntax. While actually only two exists (rows and cell contents.) Columns (or cells) is horizontal list, not vertical list.
=
== A1
== B1
= = A1 = B1
Each = A1
and = B1
represents a cell. =
in line start represents the row.
A1 cell's prefix is optional but I think it is better to not omit it to easily identify each cells and put attributes to each cells when needed.
= (thead) = A1 = (colspan 2) B1
= = (rowspan 2) A2 = B2 = C2
= = B3 = |
C3 with
multiple paragraphs
|
Formatted:
= (thead) = A1 = (colspan 2) B1 =
= = (rowspan 2) A2 = B2 = C2 =
= = B3 = |
C3 with
multiple paragraphs
| =
= (thead)
== A1 = (colspan 2) B1 =
=
== (rowspan 2) A2 = B2 = C2 =
=
== = B3 = |
C3 with
multiple paragraphs
| =
B3 having multiple paragraphs too? No problem:
+----+-------------------------------------------+
| A1 | B1 |
+----+---------------------+---------------------+
| A2 | B2 | C2 |
| +---------------------+---------------------+
| | B3 with | C3 with |
| | multiple paragraphs | multiple paragraphs |
+----+---------------------+---------------------+
= (thead) = A1 = (colspan 2) B1 =
= = (rowspan 2) = A2 = B2 = C2 =
= = |
B3 with
multiple paragraphs
| = |
C3 with
multiple paragraphs
| =
= (thead) = A1 = (colspan 2) B1 =
= = (rowspan 2) A2 = B2 = C2 =
= = |
B3 with
multiple paragraphs
| = |
C3 with
multiple paragraphs
| =
Here is table from v1 specification document: (with only indents formatted)
= = Character = Name = Categories =
= = `*` = Headings = |
- Structural
- Nestable
| =
= = `-` = Unordered Lists = |
- Nestable
| =
= = `~` = Ordered Lists = |
- Nestable
| =
= = `$` = Quotes = |
- Nestable
| =
= = `$` = Definitions = |
- Range-able
| =
= = `$` = Footnotes = |
- Range-able
| =
= = `:` = Table cells = |
- Range-able
| =
= = `%` = Atrributes = |
- Nestable
| =
Users can write table in their flow of thoughts and still have pretty good text in visual form.
Ok but how about all downsides of indent segments you've been talking about?
What happens with this syntax:
- |
- list
- list
|
Well, why don't we have both syntax and make user to choose one?
inline macro
\macro-name[nested \highlight[markup]](key pair; params)
\macro-name#[[free-form] markup]#
\macro-name[arg 1][arg 2](arg3 value; arg4)
We can solve parsing -\macro-name-
in three ways:
- force macro name to never end with punctuation characters
- force macro name to be camel-case and cannot include any non-alphabetical characters
- macro name will have higher precedence, user should use
()
to explicitly end the macro name (e.g.-\macro()-.word-
)
remove link modifier & replace it with inline macro
Lets say *bold*
is shorthand first-class syntax of \std.bold[bold]
.
Now we don't need special syntax to determine the closing modifier of bold text.
Inline macro implementation already dealed with that. We don't need two syntax doing same thing.
remove standard ranged tags
.image /path/to/image.png
@code lua
...
@end
#choice multi
- ( ) option one
- ( ) option two
- ( ) option three
@@group
paragraph
paragraph
@@end
Having multiple varients of end modifier is better than having two different verbatim ranged tag.
general macro implementation
- macros can be defined outside of norg syntax. via Janet.
- macro is a Janet function that returns Abstract Object
- to unwrap(?) a macro, Language Server first evaluate macro to AO and then see if AO is representable in pure norg syntax. Unwrap as pure norg syntax first and then see if CST is changed.
standards
Sometimes, havimg all features in first class is a bad thing. For example, dedicated syntax for todo is not a good idea considering there will also be dedicated syntax to add metadata in each list items.
But in same time, having all features out of the syntax is also a bad thing. For example, users won't love to type latex-style inline markups for simple stuffs like bold.
closing modifiers
Norg syntax usually have same, mirrored rules for opening/closing modifiers. e.g. attached modifiers, ranged tags
Only edge-case is paragraphs. paragraphs don't even have explicit opening/closing modifiers.
- multi line
paragraph
other
paragraph
We have to use empty line as a paragraph separator here.
paragraph 1
- paragraph 2
But we don't need it here.
This feels weird.
This proposal is rethinking the idea from #33 and will potentially fix #28.
It is common to represent Unicode characters in this form:
\u{1f600}
What if we say \u{...}
is a inline-macro
Instead of squarely brackets, we can use (a;b;c)
type of syntax already adopted by many syntax elements
\u(1f600)
This can also solve #28 because now escape sequence with alphabetical characters have special meaning by syntax. And escape sequence with non-alphabetical and non-punctuation characters will simply be invalid syntax.
Now problem will be: 'how to handle \u_
or \u-
?'
Example:
_word \macro_
-word \macro-
This won't be a huge deal because user can just put ()
to explicitly end the inline macro name.
_word \macro()_
-word \macro()-
This method can also be used to solve problems like "I want to place a link next to an anchor".
[anchor](){https://exmample.com}
Or maybe we can just allow this:
_word \macro\_
-word \macro\-
If we go even crazy, we can use this syntax for null modifier instead of super-verbatim:
\raw(" super-verbatim content here "; color red)
Wait... First "..." here is a key not value
How to solve this key or value problem? Swift has similar syntax where all function parameters are named by default and positional parameters should explicitly defined with
_
We can do similar thing but then we need two tokens for this(...)
syntax:
- property separation token (
,
in swift)- key identifier token (
:
in swift)Or we can ditch the concept of key and say attributes are just list of values
Todo state is not an object. It is enum. Currently applications should check at least 3 values to confirm the todo state.
x
for done=
for done (hold).
for not-todo and everything else will be undone
what if we say
;
is same as:
in swift? ...but in janet style
(foo;bar baz boo)
foo
is a positional argument. There can be only one or no positional argument- implementation may separate this positional argument (for example using
,
) but syntactically there can be only one.
- implementation may separate this positional argument (for example using
bar
after;
is a keybaz boo
is a value- as all stuff including whitespace are all value, positional argument can't come after key-value pair attributes.
- (;color red) red non-todo list item
aaaaaand this won't work because of recurring tasks:
- (+ 2023-01-01) task
-
regular markup
- bold
- italic
- underline
- strike-through
- spoiler - do we really need this as first-class citizen?
- superscript
- subscript - we need to rethink about these too
-
verbatim (allows whitespace, backslash to escape)
- inline-code
-
super-verbatim (allows whitespace, backslash as verbatim)
- math
- null
-
super-verbatim only works because there are two of them
- we can't still use
$
and%
in same time - if
$
and%
are interchangeable, they should have same (or similar) names
- we can't still use
Some points that might fix the issue:
- Why would one need whitespace and backslash in same time? Why would one need whitespace in math?
\math( a + b = c )
problems:
- How to bypass
;
character? - what happened to general key-value pair syntax?
- how to put metadata (aka. Attributes) here?
\math[a + b = c]
\math#[a + b = c]#
or in short
\m[a + b = c]
I'm not sure if
\math%[...]%
will make more sense considering null attached modifiers. Or\math@[...]@
following verbatim ranged tags.
We can solve those issues by using [...]
syntax just like links/anchors!
But now [...]
can contain any markup languages.
So [...]
in Norg generally used for "single markup content", specifically Norg in case of links/anchors.
[MyFoot]{^ myfoot}
^ myfoot
footnote paragraph
[MyFoot] <- How will be this thing rendered?
note
- (x) remove delimiting modifier
- (x) multi line headings proposal
- ( ) remove standard ranged tags
- ( ) table & list syntax proposal
<li>
<p>paragraph</p>
<p>paragraph</p>
<li>
<p>paragraph</p>
</li>
</li>
li
p paragraph
p paragraph
li
p paragraph
Norg syntax is closer to pug. It use indents as core concept to represent nesting. But instead of using whitespaces for indents, Norg merged the concepts of indents and prefix.
- list item
-- nested list item
-- nested list item
--- nested nested list item
-
character plays a role of both \t
and li
from pug.
Problem: problem from v1 spec was that a list item can only have single paragraph or following nested lists as a children. This leads several issues:
- paragraph 1
paragraph 2 <- this is not belong to the list item
- ::
paragraph 1
paragraph 2
::
v1 spec had a syntax called indent segments to wrap multiple blocks into one.
- ::
paragraph 1
- am I nested?
paragraph 2
::
-- am I nested?
근데 이러면 nesting을 나타내는 서로 다른 두가지 방식이 공존하게 됨 (range와 indent) 파싱이야 할 수 있겠지만 문법이 일관적이지 않음. 이게 왜 문제인지는 html/pug로 비유해보면 명확함:
li
<div>
p paragraph 1
li
p am I nested?
p paragraph 2
</div>
li
p am I nested?
We are basically merging two different concepts here.
Reimplementing list from scratch
li
p list item
li
p nested list item
- list item
-- nested list item
Did you notice the difference? Pargraph "list item" is also nested block with indent level 2. So if we define prefix for paragraph, it will look like this:
-
// list item
--
/// nested list item
Now we can easily put multiple paragraphs too.
-
// paragraph 1
// paragraph 2
--
/// nested list item
list item doesn't have a title
Core difference between list item and a heading is that list item doesn't have special child named "title". All children inside a list item are treated basically same.
[Suggestion]: Multi Line Heading
Commonmark supports setext headings which allows user to put multiple lines as heading content.
This is a
Setext Heading
==============
In this way, multi line heading has at least one line of spacer with multiple =
characters and they cannot be used without this spacer line. ATX heading in Commonmark can only span one line, so it can be used without any spacer line.
# ATX Heading
This doesn't belong to heading
* Multi line
Heading Title
This is not a heading
* Single line Heading *
This is not a heading
We can have similar syntax by specifying heading title as a paragraph and introducing heading closing modifier. Now you can use *\n
to explicitly end the heading title.
This allows both heading and paragraph devided by line and *heading spanning only one line (no spacer line between) in same grammar base.
You can think this syntax as a block style heading used in many programming langauges' comment section.
//// heading in comments ////
*** level 3 heading in Norg ***
As we already defined the level in heading prefix, suffix characters can repeated in any amount of times (including 0!)
Examples
Here is a list of valid syntax with this rule:
* Lv 1 Heading ******
Paragraph
****** Lv 6 Heading *
Paragraph
* Heading
in
multiple
line *
Paragraph
Issue
Problems in v1 spec
Problem 2: 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 1: 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.