← Home

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:

(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:

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:

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:

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:

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

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:

  1. property separation token (, in swift)
  2. 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.

  1. x for done
  2. = for done (hold)
  3. . 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)
- (;color red) red non-todo list item

aaaaaand this won't work because of recurring tasks:

- (+ 2023-01-01) task

Some points that might fix the issue:

\math( a + b = c )

problems:

\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

<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:

v1 spec did have this syntax with indent segments, but no one liked it.