Skip to content

Ordered-marker lettering per depth and list marker ink - #124

Draft
dylandeheer wants to merge 5 commits into
nodes-app:mainfrom
GetWeeve:feature/list-marker-styling
Draft

Ordered-marker lettering per depth and list marker ink#124
dylandeheer wants to merge 5 commits into
nodes-app:mainfrom
GetWeeve:feature/list-marker-styling

Conversation

@dylandeheer

@dylandeheer dylandeheer commented Aug 4, 2026

Copy link
Copy Markdown

Motivation

Ordered markers always render as digits, and painted markers (bullet , ordered overlays) are always bodyText. Specs that letter nested ordered lists (1. / a. / i.) or mute markers relative to content can't be expressed.

What's added

  • ListStyle.orderedMarkerStyles: [OrderedMarkerStyle] — marker rendering style per nesting depth (.numeric, .lowerAlpha, .upperAlpha, .lowerRoman, .upperRoman). Depth 0 uses the first entry and deeper levels cycle. Only the painted overlay changes — the source digits stay valid CommonMark, exactly like the existing live renumbering. Caret on the line still reveals the raw source marker. The default (a single .numeric) keeps every level numeric as before.
  • MarkdownEditorTheme.listMarker: NSColor? — ink for the painted bullet and ordered markers, including the revealed source characters inside a selection. nil (the default) keeps bodyText.

OrderedMarkerStyle.label(for:) uses bijective base-26 for alpha (27 → aa) and standard subtractive roman numerals.

Tests

Tests/MarkdownEngineTests/ListMarkerStylingTests.swift: label formatting (alpha incl. bijective rollover, roman), per-depth cycling, overlay activation for non-numeric styles, source punctuation preservation, caret reveal, listMarker slot default + custom ink.

swift build / swift test green locally (the two ScrollingHeaderController animation-timing failures are pre-existing/environmental).

Notes

Stacks on #121 AND (as of the merge commit) on #123 — the per-depth style lookup needs #123's structural list depth so a 3-column ordered ladder reaches the third style on level 3 instead of cycling back to numeric at six source columns. The diff over #121+#123 is this feature only.

Update (follow-up commit): the depth fed to orderedMarkerStyle(forDepth:) is now the structural nesting depth (indent-ladder position) rather than a spaces/2 divisor; new test pins 1. / a. / i. on a 3-column-per-level ladder.

Made with Cursor

dylandeheer and others added 3 commits August 3, 2026 22:59
The theme can recolor every element except heading text (it inherits
bodyText), and the heading config covers every metric except the face —
so an embedder that wants serif display headings over a sans body has
to fork. HeadingStyle.fontName picks a PostScript face for headings,
honored exactly so the chosen weight survives; an unresolvable name
falls back to the stock bold base font, like TaskCheckboxStyle's symbol
fallback. MarkdownEditorTheme.headingText colors heading text while the
`#` glyphs stay on headingMarker and inline constructs inside a heading
keep their own ink. Emphasis inside a heading still composes on top of
the configured face (traits added, family and per-level size kept).
Both knobs default to nil, which keeps styling identical to before.

Co-authored-by: Cursor <cursoragent@cursor.com>
Historically the visual list indent is the raw source whitespace: every
first line starts at a flat indentPerLevel, nesting shows only as the
advance of the leading spaces/tabs (two spaces = ~8pt -- far off any
design grid), and the marker-to-content gap is whatever '- ' happens to
measure per marker kind.

With markerTextGap set, markers land on a deterministic grid: a level-n
item starts at n x indentPerLevel (level 1 on the body origin), the
leading whitespace collapses (hidden-marker font; tabs advance by a
sub-point interval), and content hangs a fixed slot after the marker
for every marker kind via a kern on the final spacer char -- so bullet,
any digit count, and task content all align, wrapped lines hang at the
content edge, and the caret keeps working on real text advances. The
drawn task checkbox left-aligns to the slot origin in grid mode.

nil (the default) keeps the historical geometry exactly.

Co-authored-by: Cursor <cursoragent@cursor.com>
ListStyle.orderedMarkerStyles formats each nesting depth's painted
ordered marker (numeric / alpha / roman, cycling), so nested runs can
render 1. / a. / i. while the source keeps its literal digits -- the
same source-untouched contract display numbering already follows. A
non-numeric depth keeps the overlay active even when the computed
number matches the source, since '1.' still has to display as 'a.';
caret and selection reveal the raw digits exactly as before. The
default (a single .numeric) keeps every level numeric.

MarkdownEditorTheme.listMarker colors the painted bullet and ordered
marker glyphs independently of bodyText; nil (the default) keeps the
historical body ink.

Co-authored-by: Cursor <cursoragent@cursor.com>
dylandeheer and others added 2 commits August 4, 2026 12:53
…n checkbox to its marker slot

Two fixes to the opt-in indent grid observed in a real render:

- Grid depth now comes from the item's position in the run's indent ladder
  (seeded across scoped-restyle windows like the ordered display numbers)
  instead of a fixed 2-columns-per-level divisor, so CommonMark ordered
  nesting (3+ columns per level, driven by the parent marker width) steps
  exactly one level per parent. Legacy (nil markerTextGap) geometry is
  untouched.

- A hidden task in the grid now collapses its whole `- [ ] ` prefix so the
  box range's own position is the marker-slot origin, and the drawn square
  left-aligns there (previously the square was offset from the box chars,
  which sit AFTER the full-advance `- `, pushing it to negative x where the
  container clipped it).

Co-authored-by: Cursor <cursoragent@cursor.com>
…er style

The per-depth ordered marker style now reads the structural nesting depth
(indent-ladder position) instead of a spaces/2 divisor, so CommonMark ordered
nesting (3 columns per level) picks the next style exactly one level per
parent — `1.` / `a.` / `i.` on levels 1/2/3. This branch now builds on
feature/list-indent-grid, which introduces the shared depth computation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant