Skip to content

Renderer: align ordered list items on the widest marker of their list - #27

Open
rosekanari wants to merge 1 commit into
b451c:mainfrom
rosekanari:fix/ordered-list-alignment
Open

Renderer: align ordered list items on the widest marker of their list#27
rosekanari wants to merge 1 commit into
b451c:mainfrom
rosekanari:fix/ordered-list-alignment

Conversation

@rosekanari

Copy link
Copy Markdown

Ordered list items each hang under their own marker, so their text starts at a different x. This aligns every item of a list on the widest marker of that list.

The problem

renderListItem calls applyListParagraphStyle(_:hangingUnder:) with that item’s own prefix, which delegates to applyHangingIndent(_:indent:hangingUnder:):

style.firstLineHeadIndent = indent
style.headIndent = indent + width(of: prefix)

The body font is not monospaced, so prefixes of different items have different widths. Measured at a 21 pt body:

prefix | width | vs "1. " -- | -- | -- "1. " | 18.47 pt | — "9. " | 21.95 pt | +3.49 "10. " | 31.00 pt | +12.53 "100. " | 43.68 pt | +25.21

A ten-item list is enough to see it: the text of item 10 starts 12.53 pt further right than that of item 1. Nothing overflows — no width is imposed — but nothing lines up either.

The change

render pre-scans the joined lines and, for each contiguous run of ordered items at one indent level, computes the widest prefix; every item of the run hangs under it. The run is fully known before any item is rendered, so the widest prefix is too.

renderLine and renderListItem take it as an optional parameter defaulting to nil, so unordered items, task items and every other caller are untouched.

Two files, 69 insertions, 6 deletions.

Tests

testOrderedItemsOfOneListShareTheirHangingIndent is added. It renders a twelve-item list and asserts that all items share one headIndent. It fails on unpatched main and passes with this change. The full suite passes.

testWiderOrderedMarkerHangsFurther keeps passing, and for the right reason: it renders "1. alpha" and "10. alpha" as two separate one-item documents, so each still hangs under its own — and only — marker. Its stated intent, “a wider marker has to hang further, or 10. would overlap its own text”, is preserved: within a list, every item now hangs under the widest one, so the widest never overlaps its text.

Notes

Verified against 28d86fd. Built and tested with:

xcodebuild -project QuickMD.xcodeproj -scheme QuickMD \
           -destination 'platform=macOS' test CODE_SIGNING_ALLOWED=NO

Written with Claude Code.

Happy to split, rename, or drop the added test if you would rather keep the suite as it is

The hanging indent of an ordered item was the width of that item's own
prefix. The body font is not monospaced, so "1. " and "10. " are 18.47 pt
and 31.00 pt wide at a 21 pt body, and each item's text started at a
different x — a 12.5 pt step, visible at a glance on any list that crosses
ten items.

`render` now pre-scans the joined lines and, for each contiguous run of
ordered items at one indent level, computes the widest prefix; every item
of the run hangs under it. The run is known before any rendering, so the
widest prefix is too.

`testWiderOrderedMarkerHangsFurther` keeps passing, and for the right
reason: it renders "1. alpha" and "10. alpha" as two separate one-item
documents, so each still hangs under its own — and only — marker. Its
stated intent, "a wider marker has to hang further, or 10. would overlap
its own text", is preserved: within a list, every item now hangs under the
widest one.

Adds `testOrderedItemsOfOneListShareTheirHangingIndent`, which fails on
unpatched main and passes here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.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