xref: /dokuwiki/inc/Parsing/ParserMode/Preformatted.php (revision 945681fc9df0c43e5065e67d03a08d832e81e66c)
1be906b56SAndreas Gohr<?php
2be906b56SAndreas Gohr
3be906b56SAndreas Gohrnamespace dokuwiki\Parsing\ParserMode;
4be906b56SAndreas Gohr
571096e46SAndreas Gohruse dokuwiki\Parsing\Handler;
671096e46SAndreas Gohruse dokuwiki\Parsing\Handler\Preformatted as PreformattedHandler;
77958e698SAndreas Gohr
8be906b56SAndreas Gohrclass Preformatted extends AbstractMode
9be906b56SAndreas Gohr{
10be906b56SAndreas Gohr    /** @inheritdoc */
1171096e46SAndreas Gohr    public function getSort()
1271096e46SAndreas Gohr    {
1371096e46SAndreas Gohr        return 20;
1471096e46SAndreas Gohr    }
1571096e46SAndreas Gohr
16b1c59bedSAndreas Gohr    /**
17b1c59bedSAndreas Gohr     * Number of leading spaces that trigger a preformatted block.
18b1c59bedSAndreas Gohr     *
1947a02a10SAndreas Gohr     * DokuWiki's historical value is 2 spaces; Markdown uses 4. When the
2047a02a10SAndreas Gohr     * active parse prefers Markdown (md or md+dw) we flip to 4 so indented
2147a02a10SAndreas Gohr     * code blocks match GFM. A single tab is always a trigger regardless
2247a02a10SAndreas Gohr     * of the space threshold.
23b1c59bedSAndreas Gohr     */
24b1c59bedSAndreas Gohr    protected function getIndentWidth(): int
25b1c59bedSAndreas Gohr    {
2647a02a10SAndreas Gohr        return $this->registry->isMdPreferred() ? 4 : 2;
27b1c59bedSAndreas Gohr    }
28b1c59bedSAndreas Gohr
2971096e46SAndreas Gohr    /** @inheritdoc */
30be906b56SAndreas Gohr    public function connectTo($mode)
31be906b56SAndreas Gohr    {
32b1c59bedSAndreas Gohr        $indent = str_repeat(' ', $this->getIndentWidth());
33b1c59bedSAndreas Gohr
34*945681fcSAndreas Gohr        // An indented line is ambiguous with a list item, so the entry
35*945681fcSAndreas Gohr        // patterns must not swallow a line whose indent begins a list - the
36*945681fcSAndreas Gohr        // list has to win. In base mode sort order already gives the list mode
37*945681fcSAndreas Gohr        // the tie, but table mode connects preformatted as a protected
38*945681fcSAndreas Gohr        // sub-mode without connecting any list mode, so this lookahead is the
39*945681fcSAndreas Gohr        // only thing that lets an indented list end the table there (in every
40*945681fcSAndreas Gohr        // syntax the table exists in, including the mixed dw+md / md+dw
41*945681fcSAndreas Gohr        // combinations). The guard tracks whichever list mode is actually
42*945681fcSAndreas Gohr        // loaded. DokuWiki's Listblock treats a bare * or - as a marker even
43*945681fcSAndreas Gohr        // with no following space, so the guard rejects those two characters
44*945681fcSAndreas Gohr        // outright. Markdown's GfmListblock only starts a list when one of its
45*945681fcSAndreas Gohr        // markers - a bullet -, *, + or a digit run ending in . or ) - is
46*945681fcSAndreas Gohr        // followed by whitespace or the end of the line, so the guard mirrors
47*945681fcSAndreas Gohr        // that and still lets an indented code block that merely starts with
48*945681fcSAndreas Gohr        // such a character (e.g. a *** line) through.
49*945681fcSAndreas Gohr        $listGuard = $this->registry->isMdPreferred()
50*945681fcSAndreas Gohr            ? '(?!(?:[-*+]|\d{1,9}[.)])[ \t\n])'
51*945681fcSAndreas Gohr            : '(?![\*\-])';
52*945681fcSAndreas Gohr
53*945681fcSAndreas Gohr        $this->Lexer->addEntryPattern('\n' . $indent . $listGuard, $mode, 'preformatted');
54*945681fcSAndreas Gohr        $this->Lexer->addEntryPattern('\n\t' . $listGuard, $mode, 'preformatted');
55be906b56SAndreas Gohr
56259e91d9SAndreas Gohr        // match continuation lines inside the preformatted block
57b1c59bedSAndreas Gohr        $this->Lexer->addPattern('\n' . $indent, 'preformatted');
58be906b56SAndreas Gohr        $this->Lexer->addPattern('\n\t', 'preformatted');
59be906b56SAndreas Gohr    }
60be906b56SAndreas Gohr
61be906b56SAndreas Gohr    /** @inheritdoc */
62be906b56SAndreas Gohr    public function postConnect()
63be906b56SAndreas Gohr    {
64f57da51cSAndreas Gohr        // Two exits: a zero-width lookahead when the next line starts with
65f57da51cSAndreas Gohr        // non-whitespace content (so the boundary \n stays in the stream
66f57da51cSAndreas Gohr        // and downstream block-level modes like GfmHr or GfmHeader can
67f57da51cSAndreas Gohr        // anchor on it), and a consuming \n fall-through for blank lines
68f57da51cSAndreas Gohr        // and end-of-input. The lookahead-only branch is registered first
69f57da51cSAndreas Gohr        // so PCRE's leftmost-first alternation prefers it whenever it
70f57da51cSAndreas Gohr        // applies; the consuming branch handles the cases where it cannot.
71f57da51cSAndreas Gohr        $this->Lexer->addExitPattern('(?=\n[^ \t\n])', 'preformatted');
72be906b56SAndreas Gohr        $this->Lexer->addExitPattern('\n', 'preformatted');
73be906b56SAndreas Gohr    }
74be906b56SAndreas Gohr
75be906b56SAndreas Gohr    /** @inheritdoc */
7671096e46SAndreas Gohr    public function handle($match, $state, $pos, Handler $handler)
77be906b56SAndreas Gohr    {
7871096e46SAndreas Gohr        switch ($state) {
7971096e46SAndreas Gohr            case DOKU_LEXER_ENTER:
8071096e46SAndreas Gohr                $handler->setCallWriter(new PreformattedHandler($handler->getCallWriter()));
8171096e46SAndreas Gohr                $handler->addCall('preformatted_start', [], $pos);
8271096e46SAndreas Gohr                break;
8371096e46SAndreas Gohr            case DOKU_LEXER_EXIT:
8471096e46SAndreas Gohr                $handler->addCall('preformatted_end', [], $pos);
8571096e46SAndreas Gohr                /** @var PreformattedHandler $reWriter */
8671096e46SAndreas Gohr                $reWriter = $handler->getCallWriter();
8771096e46SAndreas Gohr                $handler->setCallWriter($reWriter->process());
8871096e46SAndreas Gohr                break;
8971096e46SAndreas Gohr            case DOKU_LEXER_MATCHED:
9071096e46SAndreas Gohr                $handler->addCall('preformatted_newline', [], $pos);
9171096e46SAndreas Gohr                break;
9271096e46SAndreas Gohr            case DOKU_LEXER_UNMATCHED:
9371096e46SAndreas Gohr                $handler->addCall('preformatted_content', [$match], $pos);
9471096e46SAndreas Gohr                break;
9571096e46SAndreas Gohr        }
9671096e46SAndreas Gohr
9771096e46SAndreas Gohr        return true;
98be906b56SAndreas Gohr    }
99be906b56SAndreas Gohr}
100