Skip to main content

Module select

Module select 

Source
Expand description

Selecting part of a note the way an Obsidian link fragment does: [[Note#Heading]], [[Note#Parent#Child]] and [[Note#^block-id]].

This reads the body as lines, not through the crate::DocTree projection, because the two answer different questions. The tree makes the first H1 the document title, while a link fragment treats every heading the same: #Title selects the H1 section like any other heading. The rules are Obsidian’s:

  • A heading is an ATX heading (# to ######, then whitespace, then text), up to three spaces in. A closing # run counts only after a space or tab, so ## Learn C# is the heading Learn C#. Headings inside fenced code are not headings (fences as CodeRules::SkillExport reads them).
  • A section is its heading line through the line before the next heading of the same or a shallower level, so subsections come with it.
  • A heading path (Parent#Child) names the heading by its last segment; the segments before it must be the nearest headings above it, in order (plain heading ancestry: an H1 is an ancestor like any other heading). The first heading that matches wins.
  • A selector is first tried as one heading’s whole text, since a heading may contain #; only when no heading has that text is it split on # into a path.
  • A block ^id is labelled by a marker at the end of a line (after whitespace) or alone on a line. Alone on its line, it labels the paragraph or list above it, blank lines skipped. At the end of a list item it labels that one item. At the end of any other line it labels the paragraph the line ends, walked back to a blank line or a heading. The marker is never part of the selection.

Every selection is trimmed of surrounding whitespace. The *_range functions give the selection’s bytes in the body; the others give the text, which is the same bytes with each \r\n read as \n.

The same rules back crate::DocTree::section_by_heading_path, crate::DocTree::section_by_selector and crate::DocTree::block_range_by_id.

use markdown_contract::{parse_document, select};

let note = "---\ntitle: x\n---\n# Guide\n\n## Setup\n\nInstall it. ^install\n\n## Use\n";
let tree = parse_document(note);

let range = tree.section_by_heading_path(&["Guide", "Setup"]).unwrap();
assert_eq!(&tree.body[range], "## Setup\n\nInstall it. ^install");
assert_eq!(select::block(&tree.body, "install").as_deref(), Some("Install it."));
assert_eq!(
    tree.block_by_id("install").unwrap().range(),
    tree.root.sections[0].blocks[0].range()
);

Functions§

atx_heading
An ATX heading’s level and text: ^ {0,3}(#{1,6})\s+(.+?)\s*$, with a closing # run stripped when a space or tab precedes it. A bare ## or ## is not a heading; ## (whitespace for text) is one with empty text, which ends sections but can never be selected.
block
block_range as text.
block_range
The block ^id labels; see the module doc for the three shapes.
fragment
fragment_range as text.
fragment_range
What a link fragment selects: no fragment the whole body, ^id a block, anything else a heading selector (heading_range). All trimmed.
heading
heading_range as text.
heading_path
heading_path_range as text.
heading_path_range
The section of the first heading whose text is path’s last segment and whose nearest ancestors are the segments before it. Segments are compared exactly, as given; an empty path selects nothing.
heading_range
The section a link fragment’s heading selector names: the whole trimmed selector as one heading’s text first, then the selector split on # (empty segments dropped, each trimmed) as a heading path.