From b9b2876ba7790b0e92e2c66f516d374ce7d53905 Mon Sep 17 00:00:00 2001 From: "Jason A. Dönerfield" Date: Sat, 18 Jul 2026 21:13:31 +0200 Subject: md2html: do not loosen tight lists with multi-line items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list-interrupt preprocessor inserted a blank line before any bullet that followed a non-item, non-4-space-indented line. When a list item wrapped onto a 2-space-indented continuation line, the *next* item looked like a paragraph->list transition, so a blank got injected between items — turning a tight list into a loose one (every
  • wrapped in

    ), as seen in docs/internals.md "Writing programs in C". Track list state and only interrupt an actual paragraph: a bullet that continues an already-open list (or its indented continuation lines) no longer triggers a blank-line insert. The original paragraph->list interrupt and the "1985." ordered-list guard are unchanged. --- filters/html-converters/md2html | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html index 79d4a17..d2906ec 100755 --- a/filters/html-converters/md2html +++ b/filters/html-converters/md2html @@ -31,6 +31,13 @@ _MD_ANYITEM = re.compile(r'^ {0,3}(?:[-*+]|\d{1,9}[.)])[ \t]') class _ListInterruptPreprocessor(Preprocessor): def run(self, lines): out, in_fence, fence = [], False, None + # Track whether we're already inside a list. A bullet that merely + # continues an existing list must NOT get a blank line inserted before + # it: that would turn a tight list into a loose one (every item wrapped + # in

    ), which is exactly what happens when a preceding item wraps onto + # an indented continuation line and we mistake the next bullet for a + # paragraph->list transition. We only want to interrupt a real paragraph. + in_list = False for line in lines: m = _MD_FENCE.match(line) if m: @@ -41,12 +48,28 @@ class _ListInterruptPreprocessor(Preprocessor): in_fence, fence = False, None out.append(line) continue - if not in_fence and out: + if in_fence: + out.append(line) + continue + if not line.strip(): + # a blank line doesn't end a list (loose lists / lazy + # continuation), so leave in_list untouched + out.append(line) + continue + is_item = bool(_MD_ANYITEM.match(line)) + indented = line[:1] in (' ', '\t') + # a non-indented, non-item line after a list closes that list + if in_list and not is_item and not indented: + in_list = False + if out: prev = out[-1] - if (prev.strip() and not _MD_ANYITEM.match(prev) + if (prev.strip() and not in_list + and not _MD_ANYITEM.match(prev) and not prev.startswith((' ', '\t')) and (_MD_BULLET.match(line) or _MD_ORDERED1.match(line))): out.append('') + if is_item: + in_list = True out.append(line) return out -- cgit v1.3.1-sl0p