`-`
` inside `
  • ` is never * desirable and section nesting must not span into items) and gfm_listblock * itself (defensive guard against lexer re-entry on pathological inputs; * normal nested lists are caught by the outer pattern instead). * * Each item's sub-parsed calls are wrapped in a `nest` instruction (see * Handler\Nest) before they reach the outer handler. This is essential: * the sub-parser's Block rewriter has already wrapped multi-paragraph * content in `p_open`/`p_close`, and without nest-wrapping the main * handler's Block rewriter would see those paragraphs and add another * `

    ` around the entire replayed range, producing nested `

    ` tags. * Block treats `nest` as opaque and the renderer base class unwraps it * transparently — the same pattern Footnote uses. * * Indentation rule: depth = (indent / 2) + 1. Tabs become two spaces. 1- and * 3-space indents round down. Marker characters: -, *, + (unordered) and * digits followed by . or ) (ordered). Nested lists are caught by the * outer pattern (each marker at any 2-space-multiple indent is its own * item at the corresponding depth) and stitched back into nested HTML by * the GfmLists rewriter. */ class GfmListblock extends AbstractMode { /** * Regex fragment matching one list marker. * * Either an unordered marker (`-`, `*`, `+`) or an ordered marker * (1-9 digits followed by `.` or `)`). Used by the entry pattern in * connectTo() and by the per-line classifier in parseItems(). */ protected const MARKER = '(?:[-*+]|\d{1,9}[.)])'; /** @inheritdoc */ public function getSort() { return 10; } /** @inheritdoc */ public function preConnect() { ModeRegistry::getInstance()->registerBlockEolMode('gfm_listblock'); } /** * Register the special pattern that captures a whole list block. * * The pattern starts on a marker line (any indent) and then loops over * four alternatives until none matches: * * 1. A subsequent marker line at any indent. * 2. An indented continuation line (>= 2 leading spaces with content). * 3. A blank line followed by indented content (any number of * intervening blank lines tolerated via the lookahead). * 4. A blank line followed by a next marker (same multi-blank * tolerance as alt 3). * * The block ends naturally when none of the alternatives match — for * example a column-0 non-marker line, or two-or-more blank lines * followed by non-list content. * * @inheritdoc */ public function connectTo($mode) { $pattern = '\n[ \t]*' . self::MARKER . '(?:[ \t][^\n]*|(?=\n))' . '(?:' . '\n[ \t]*' . self::MARKER . '(?:[ \t][^\n]*|(?=\n))' . '|' . '\n[ \t]{2,}\S[^\n]*' . '|' . '\n[ \t]*(?=(?:\n[ \t]*)*\n[ \t]{2,}\S)' . '|' . '\n[ \t]*(?=(?:\n[ \t]*)*\n[ \t]*' . self::MARKER . ')' . ')*'; $this->Lexer->addSpecialPattern($pattern, $mode, 'gfm_listblock'); } /** * Convert the captured block into handler calls. * * Sequence: * 1. parseItems() splits the captured text into per-item records. * 2. Install GfmLists as a CallWriter rewriter on the main handler. * 3. Emit list_open carrying the first item's marker — the rewriter's * handleListOpen opens the `