Inkpot
Inkpot
Inkpot is something I've been wanting to work on forever. A writing platform. Not much progress has been made on it yet, besides a simple spec having been written up for the markup language, but watch this space!
The spec mentioned above is subject to change but it is intended to be a markup syntax for planning and writing novels. One idea is to include two types of header:
- Visible headers; these will be included in the output, an example of which might be chapter headings
- Non-visible headers; these will not be included in the output but the body of text beneath them will be, they are intended for organising a text
Below is an example of what this might look like, with = denoting visible headers and - denoting non-visible ones.
= My Manuscript
== Chapter One
--- Scene 1
Once upon a time...
--- Scene 2
And now that our stage is set...
== Chapter Two
--- Scene 3
The story continues...
== Chapter Three
...
A program interpreting the above syntax might include a feature like folds for each scene. A GUI application might allow for us to grab and reorganise the scenes if we decide that parts of our story feel out of order. Our manuscript might be represented as a tree appearing like:
h1. My Manuscript
h2. Chapter One
h3. Scene 1
h3. Scene 2
h2. Chapter 2
h3. Scene 3
h2. Chapter 3
Another idea I wanted to implement inspired by Emacs' Org Mode is todo states. States I've had in mind are "TODO", "DRAFT" and "DONE". For example...
= DRAFT My Manuscript
== DRAFT Chapter One
--- DONE Scene 1
Once upon a time...
--- DONE Scene 2
And now that our stage is set...
== DRAFT Chapter Two
--- DRAFT Scene 3
The story continues...
== TODO Chapter Three
...
The above example uses capitalised keywords similar to Emacs' Org Mode. But an alternative approach could use a different syntax:
[ ],[~],[x][TODO],[DRAFT],[DONE]
Alternatively, the syntax could be directly related to the heading tree like so:
==would denote a visible heading--would denote a non-visible heading~~would denote a draft section
A drawback of this approach is it represents only three of the six possible states:
- Visible heading, todo
- Visible heading, draft
- Visible heading, done
- Non-visible heading, todo
- Non-visible heading, draft
- Non-visible heading, done
Even if we were to fuse the todo and draft states into one needs work state, we still only meet three out of the four possible state conditions.
- Visible heading, todo/draft
- Visible heading, done
- Non-visible heading, todo/draft
- Non-visible heading, done
Only conditions 2 (==), 3 (--) and 4 (~~) are met. While at first thought that might make sense (a todo/draft section shouldn't be published anyway, right? it could share the ~~ heading syntax), there are times where we should like to publish our work in progress and still differentiate between visible and non-visible headings.
Compounding the issue, we might also want some way to add a draft date or draft number to our sections. Could this number also be part of the heading? What would that look like?
So, we need one more symbol and maybe to consider the inclusion of a draft number that should always be non-visible in the published output. This draft number should not conflict with a numeric title for the section, of any format. And this is a problem of the Org Mode style too. Consider wanting to have a title like TODO Headings; here, TDOD would be interpreted as a todo state. One could work around this by writing DRAFT TODO Headings, but it's clunky. I'm thinking of a solution more like...
==[1] Visible Heading for a First Draft
--[1] Non-visible Heading for a First Draft
~~[2] Non-visible Heading for a Second Draft In Progress
OR
==[2025-11-01] Visible Heading for a November 1st Draft
--[2025-11-06] Non-visible Heading for a November 6th Draft
~~[2025-11-28] Non-visible Heading for a November 28th Draft In Progress
We might call this an optional draft info feature.
Inspired by this, perhaps we might imagine some alternative syntax for denoting visible vs non-visible. Something like a tag or flag for non-visible headings. Consider:
===[1]:x
Here, the = symbol indicates that this section is "done" but its heading is non-visible due to the :x tag.
---[1]:x
Here, the - symbol indicates that this section is "in-progress" (todo or draft) and its heading is non-visible due to the :x tag.
This approach isn't the most efficient in terms of how many characters we need to change to update todo vs done status. A variable amount of characters are changed depending on heading level. Perhaps the inverse makes more sense:
===[1:_]
Here, the = symbol indicates that this heading is visible and the '_' indicates that the draft is still todo.
---[1:~]
Here, the - symbol indicates that this heading is non-visible and the '~' indicates that the draft is in-progress.
===[1:x]
Here, the = symbol indicates that this heading is visible and the 'x' indicates that the draft is complete.
Note the use of a colon separating the draft number from the todo state above. This union of the two pieces of information is really nice, I think, communicating at a glance that this is part of draft one and we're working on it (1:~). Either piece of information could be omitted. All of the following headings would be valid:
=== Heading
===[1] Heading
===[_] Heading
===[1:~] Heading
Note that === Heading and ===[x] Heading would be viewed as equivalent by a syntax interpreter. The default state ought to be to treat a non-marked section as part of a finished draft and publish it.
NOTE:
A reason I really like the symbols -, = and [] is that these can all be typed without any modifier key. None of theme require SHIFT.
Let's take another shot at what a complete document might look like, using this new syntax:
=[1:~] My Manuscript
==[1] Chapter One
---[1] Scene 1
Once upon a time...
---[1] Scene 2
And now that our stage is set...
==[1:~] Chapter Two
---[1:~] Scene 3
The story continues...
==[1:_] Chapter Three
...
The above isn't super clear without syntax highlighting. We might consider this a shorthand for a more explicit syntax like:
=[1:DRAFT] My Manuscript
==[1:DONE] Chapter One
---[1:DONE] Scene 1
Once upon a time...
---[1:DONE] Scene 2
And now that our stage is set...
==[1:DRAFT] Chapter Two
---[1:DRAFT] Scene 3
The story continues...
==[1:TODO] Chapter Three
...
Also note that while we might treat the draft number or date and the todo status as official features (coming first and second in the metadata brackets), the syntax should be treated as expandable. For example, someone might want to include both a draft number AND and draft date. The draft date in this case could be included as a third value:
=[1:TODO:2025-11-28]
Alternatively, we might write some regular expression that allows for these to be written in any order with draft number and draft date both officially supported. Such a regex would look something like:
(?<=[=-]+\[)(?:(?:(\d+)|(TODO|DRAFT|DONE|_|~|x)|(\d{4}-\d{2}-\d{2}))(?:\:?))+(?=\])
This probably needs some work. An alternative approach would be to take the entire string between the square brackets, split this on : and match each part independently.
NOTE: Rust's regex engine does not support look arounds. I use two of these in the above example. When writing a syntax parser in Tree Sitter, I do not know what this means. Tree Sitter does allow for the regex to be written as JavaScript regular expressions but it generates its own logic based on that input using Rust's regex syntax instead. We might find we're better off writing in the Rust syntax directly!
One more thing for now...
It occurs to me that TODO, DRAFT and DONE are not full representative of the states an author may wish to see represented. At least one more we might like to add is something like DELETED, EXCLUDED or ARCHIVED which would denote an entire section that ought not to be published (unless specifically flagged to do so). The utility of this would be in retaining written sections that have been omitted from future publications. This could be scenes that got cut entirely or it could be the older versions of redrafted sections. One might write something like...
--[1:DELETED] Scene 1
This is my first version of the scene...
--[2:DONE] Scene 1
This is my rewrite. It's much better!
The DELETED status should also have a shorthand, and I actually really like x for this (which I've suggested above ought to denote a section marked as DONE). Consider...
-- A Completed Scene
--[!] A Completed Scene with Alternative Syntax
--[_] A Scene Marked TODO
--[~] A Scene Marked as DRAFT ("In-Progress")
--[x] A Deleted Scene
Though, having written out the above, I might quite like to add an ATTENTION status which would mark a section as needing attention. This would be used to mark a finished scene as in need of another pass, whether or not a rewrite or edit is actually necessary. For that [!] seems perfect, but [?] might also feel applicable. This just highlights the confusion these shorthand terms can cause, whereas tags like [TODO], [DRAFT], [DONE], [DELETED] and [ATTENTION] are immediately much more clear about the intent.
Let's create a table with our considerations side-by-side:
| Longform | Shorthand | Status |
|---|---|---|
[TODO] | [_] | Todo |
[DRAFT] | [~] | In-Progress |
[DONE] | [*] | Completed |
[ATTENTION] or [ATTN] | [!] | Needs attention |
[DELETED] or [DEL] | [x] | Deleted |
NOTE: The [?] shorthand also seems like a good idea, but denoting what? Probably a more specific kind of attention. Rather than just requiring another pass, this could mean we have serious questions about the section like... "why did I even write this?", "does this make any sense?". In which case it means something like "questionable", which might be described by the longform syntax [QUESTIONS] or [QSTNS].
A note about collaborative documents...
The thought occured to me to also consider the mark @. Initially I thought this could indicate the user's position in a manner of speaking, a sort of bookmark for where they left off writing or editing. That's one idea. Another may be as a tag for collaborative writing/editing. For example...
--[@thombruce] Thom Wrote This Section
--[@johndoe] John Wrote This One
This is another informative piece of metadata that could find its way into section headings.
If two people had worked on a section, this may be indicated by a comma-separated list:
--[2:@thombruce,@johndoe] Thom and John Have Each Had Contributed to This
In the above example, I include a draft number so as to imply that each contributor might increment this when amending the section. It should also be the implied case that the contributors are listed in order of their contributions. It may also be possible to repeat names:
--[4:@thombruce,@johndoe,@janesmith,@thombruce]
Here, Thom wrote the initial draft before John and Jane each made their contributions. It was then turned back over to Thom for a final touch-up. A complete history of the section heading might look like this:
--[_] Heading
--[1:~:@thombruce] Heading
--[2:x:@thombruce,@johndoe] Hednig
--[2:?:@thombruce,@johndoe] Heading
--[3:!:@thombruce,@johndoe,@janesmith] Heading
--[4:*:@thombruce,@johndoe,@janesmith,@thombruce] Heading
Take note of the questionable changes made by John (inclusive of marking the section as deleted with [x]) before the heading was marked as [?] by an anonymous other party. Jane then takes a shot at editing the section and marks it as needing attention before Thom completes the draft.
In many cases, changes and tags ought to echo up the syntax tree. A level one heading with any amount of TODO or DRAFT subheadings should itself be marked as TODO or DRAFT and we should expect tools to automate this. If the larger section has been completed and some section within it becomes marked for ATTENTION or QUESTIONS, then it too should be marked for ATTENTION or QUESTIONS; again, we expect tools to automate this.
The top-level heading should also inherit the highest value of draft number and/or draft date from its descendants, as well as all contributors (though we shouldn't expect these to be listed in the top-level heading unless explicitly added).
As a final consideration, think about scriptable sections. For example, given that authors are tagged against sections we might have some clever utility for listing the authors somehwere in the output. Something like...
=[2:*] A Document
by :authors
==[2:*] Part One
---[1:*:@thombruce] Section 1
---[2:*:@johndoe,@thombruce] Section 2
Here, the :authors tag would be expected to output something like thombruce and johndoe (perhaps each could be associated with a table entry elsewhere giving proper names and links to each author). A publisher should expect the order of names to reflect the size of contributions by default (in the example above, thombruce has contributed to more sections than johndoe, though other ways of determining the order should be supported).
NOTE: This is just an illustration; I have no idea if I intend to implement a feature like this or what the final syntax would look like.
So, how do we make this syntax a reality?
- We make it a syntax I can highlight in Neovim (and any other program using Tree Sitter; we make it a Tree Sitter syntax)
- We create some custom conversion tooling for the command-line which, given the rules described, transforms the content into a publishable form (we probably use Pandoc for this)
- We start making use of it and iterate on the concept from there
Most authors don't want to write their prose in a program like Neovim. We should think about also implementing the syntax in our own GUI program, Inkpot. This will require some kind of WYSIWYM editor interface which conceals the syntax and presents its features to the user in a more sanitised fashion. To make that a reality we...
- Start by choosing a programming language and (likely) an appropriate framework
- Integrate our custom syntax into the program... probably by integrating Tree Sitter; it's a single dependency with numerous language bindings to pick from