caretline

Markdown documents

caretline’s block grammar is Markdown’s: list markers, numbered items, headings, quotes and fences start blocks, and Enter, Backspace and paste follow Markdown list editing. This page is that grammar and its rules. The blocks themselves (identity, the caret, block operations, the layout, decorations, folds) are on structure.md.

It lives in src/outline (markdown.rs and the rules in rules.rs) and is on when state.doc.outline is set. Nothing in it gives a line a meaning beyond its Markdown shape: a host that gives [x] a meaning does that itself, with a host command.

$ caretline --outline crates/caretline-app/fixtures/trip.md

Markers

A block’s first line is indent marker content:

MarkerBlockkind, hang
- , * , + A bulletBullet, Bullet
- [c] with c in OutlineConfig::tagsA tagged bullet (see Tags)Bullet, Bullet, tag: Some(c)
12. or 12) (with numbered)A numbered itemBullet, Number(12)
# , ## , ### A heading paragraphPara, Heading(n)
> A quote paragraphPara, Quote
Three backticksA code fence: every line to the closing fence is its content, and no marker inside starts a blockPara, Fence
noneA paragraphPara, None

Paragraphs nest like items: a nested paragraph’s first line is its indentation and its content ( first point is a paragraph at depth 1). A block that is exactly one image (![caption](path), with atomic_images) is atomic: one unit for the caret.

Tags

A bullet may carry a one-character tag in brackets, - [c] , the bracket syntax of GitHub-flavoured Markdown lists among others. Tags are off by default: - [x] milk is a bullet whose text is [x] milk. With OutlineConfig::tags set, a tag whose character is in it is part of the marker:

caretline gives a tag no meaning. A host that does (a status, a priority, a label) adds the rest: commands that change tags, decorations that draw them, and its own reading of BlockInfo::tag. embedding.md builds tasks this way.

Blank rows

Unset, a block’s blank row follows its kind and the block before it: a paragraph has one before and after it (a ## or ### heading only before), list items are tight, and the first block has none. gap: Some(true) or Some(false) on its mark overrides that.

The rules

Each rule is one transaction and one undo step. The marks column says what happens to ids.

MsgWhereDoesMarks
insert_newline (Enter)In a list itemSplits it: the rest goes to a new item with the same marker and indentation (a number goes up by one, a tag becomes new_tag)The new item gets a new id
At an item’s content startA new empty item aboveThe item keeps its id
On an empty itemIt becomes a paragraph (the list ends)Kept
In a paragraphA soft break—
At a paragraph’s very startA new empty paragraph aboveThe paragraph keeps its id
At the start of a paragraph’s later lineThat line starts a new paragraph (an empty line there is dropped when more follows). So Enter twice at a paragraph’s end starts a new paragraphNew id for the new paragraph
At the end of a paragraph’s line, with more lines belowThe rest becomes a new paragraph, with the caret on a new empty one betweenTwo new ids
In a heading or quoteA new paragraph after it (at its content start: a new empty paragraph above)New id
In a fenceA line break—
On a selected atomic blockA new empty paragraph after itNew id
soft_break (Shift-Enter, Ctrl-J)In an item, heading or quoteA line break inside the block—
ElsewhereAs Enter
delete_backward (Backspace) at a content startA tagged bulletRemoves the tag: a plain bulletKept
A bullet, numbered item, heading or quoteRemoves the marker and indentation: a paragraphKept
A paragraph after a paragraphJoins them, keeping the line breakThe lower id goes
A paragraph after anything elseJoins it onto the block above’s last lineThe lower id goes
After an atomic blockSelects that block (a second Backspace removes it)—
delete_forward (Delete) at a block’s endJoins the next block in (its marker goes); before an atomic block, selects itThe next id goes
delete_word_*, delete_to_line_*, kill_lineAt a block’s edgeAs Backspace or Delete
Inside a blockAs in plain text, but never past the content start or into the next line
Backspace or DeleteOn a selected atomic blockRemoves the block; the status says whatIts id goes (undo brings it back, selected)
insert_textOn a selected atomic blockA new paragraph after it with the textNew id
indentOn a later line of a paragraph (a caret, no selection)That line becomes a paragraph of its own, one level under the paragraphNew id for the line
pasteMarkdown with line breaksRead into blocks: the first joins the text before the caret (taking its shape when there is none), the rest follow, and the text after the caret ends the last. Images are left out and countedNew ids
Whole blocks from the register, on an empty itemThey take the item’s place, with their own kinds and tags, re-indented to its depthThe cut ids come back
Whole blocks from the register, at a block’s endThey follow the block’s subtree as siblings, at its depthThe cut ids come back
Whole blocks from the register, inside a block’s textAs pasted MarkdownNew ids
The register (or the same text from the system clipboard)Pasted as it was cutThe cut ids come back
Whole blocks from the register, over a selection of whole blocksThe selected blocks go and the register’s take their place, in one stepThe cut ids come back
paste_plain (Alt-V)Paragraphs with their line breaks kept; nothing becomes a listNew ids
copy / cutInside one blockPlain textA cut keeps the removed ids in the register
Across blocksMarkdown: the first block’s text from the selection’s start (its marker only from its content start), then each block with its marker and indentation, a blank line around paragraphs
Whole blocksThe register takes their lines with markers and indentation; a cut takes the lines out, leaving no empty itemA cut keeps the ids in the register

Tab, Shift-Tab, moving blocks and folds are block operations.

Markdown in and out

outline::markdown reads and writes Markdown:

A file round-trips exactly when it is written the way to_file writes it. Three things don’t survive a file: an empty paragraph is left out, an empty continuation line reads back as a blank row (splitting the block), and two paragraphs with no blank row between them read back as one.

use caretline::outline::markdown;
use caretline::{update, By, Dir, Msg, OutlineConfig, Viewport};

let mut s = markdown::load("- Pay rent\n", None, Viewport { width: 40, height: 6 }, OutlineConfig::default());
update(&mut s, Msg::Move { dir: Dir::Forward, by: By::LineEnd, extend: false });
update(&mut s, Msg::InsertNewline);
update(&mut s, Msg::InsertText { text: "Call Ana".into() });
update(&mut s, Msg::Indent);
assert_eq!(markdown::to_file(&s), "- Pay rent\n  - Call Ana\n");

OutlineConfig

FieldDefaultMeaning
indent2Spaces per depth on a block’s first line
tags""The characters a bullet’s [c] tag may be; empty: brackets are text
new_tagnoneThe tag Enter gives the item after a tagged one
atomic_imagestrueA block that is exactly one image is one caret unit
numberedtrue12. and 12) start numbered items

Keys

An outline document’s keymap is the plain one with these on top (see keys.md):

KeyCommandMsg
Tab / Shift-Tabstructure.indent / structure.outdentindent / outdent
Shift-Enter, Ctrl-Jedit.soft_breaksoft_break
Alt-↑ / Alt-↓structure.move_up / structure.move_downmove_block
Ctrl-↑ / Ctrl-↓move.block_up / move.block_down (Shift: select.*)move by block
Alt-Vclip.paste_plainpaste_plain

Try it

$ caretline --outline crates/caretline-app/fixtures/trip.md --keys '<down><down><down><down><end><cr><tab>Call Ana' --snapshot 50x18
$ caretline --state crates/caretline-app/fixtures/outline-edited.state.json --snapshot 50x18
$ caretline --outline crates/caretline-app/fixtures/trip.md --keys '<d-down>!<c-s>' --effects

--layout adds the outline layout, with plain hang glyphs:

$ caretline --layout crates/caretline-app/fixtures/trip.md --snapshot 50x16 --no-status-bar
  #   Lisbon trip

      Booked the flat in Lisbon.
      It faces the river.

  •   Pay the deposit
      •   ask Ana about her desk
  •   Book flights
  •   Renew passport

      ![boiler label](files/boiler.png)

  1.  Pack
  2.  Leave

This page on GitHub: docs/caretline/markdown.md