xref: /dokuwiki/inc/Parsing/ParserMode/Preformatted.php (revision 75364f13219a5af44f52c564ea0a62df64c3a17f)
1<?php
2
3namespace dokuwiki\Parsing\ParserMode;
4
5use dokuwiki\Parsing\Handler;
6use dokuwiki\Parsing\Handler\Preformatted as PreformattedHandler;
7
8class Preformatted extends AbstractMode
9{
10    /** @inheritdoc */
11    public function getSort()
12    {
13        return 20;
14    }
15
16    /**
17     * Number of leading spaces that trigger a preformatted block.
18     *
19     * DokuWiki's historical value is 2 spaces; Markdown uses 4. When the
20     * active parse prefers Markdown (md or md+dw) we flip to 4 so indented
21     * code blocks match GFM. A single tab is always a trigger regardless
22     * of the space threshold.
23     */
24    protected function getIndentWidth(): int
25    {
26        return $this->registry->isMdPreferred() ? 4 : 2;
27    }
28
29    /** @inheritdoc */
30    public function connectTo($mode)
31    {
32        $indent = str_repeat(' ', $this->getIndentWidth());
33
34        $this->Lexer->addEntryPattern('\n' . $indent, $mode, 'preformatted');
35        $this->Lexer->addEntryPattern('\n\t', $mode, 'preformatted');
36
37        // match continuation lines inside the preformatted block
38        $this->Lexer->addPattern('\n' . $indent, 'preformatted');
39        $this->Lexer->addPattern('\n\t', 'preformatted');
40    }
41
42    /** @inheritdoc */
43    public function postConnect()
44    {
45        // Two exits: a zero-width lookahead when the next line starts with
46        // non-whitespace content (so the boundary \n stays in the stream
47        // and downstream block-level modes like GfmHr or GfmHeader can
48        // anchor on it), and a consuming \n fall-through for blank lines
49        // and end-of-input. The lookahead-only branch is registered first
50        // so PCRE's leftmost-first alternation prefers it whenever it
51        // applies; the consuming branch handles the cases where it cannot.
52        $this->Lexer->addExitPattern('(?=\n[^ \t\n])', 'preformatted');
53        $this->Lexer->addExitPattern('\n', 'preformatted');
54    }
55
56    /** @inheritdoc */
57    public function handle($match, $state, $pos, Handler $handler)
58    {
59        switch ($state) {
60            case DOKU_LEXER_ENTER:
61                $handler->setCallWriter(new PreformattedHandler($handler->getCallWriter()));
62                $handler->addCall('preformatted_start', [], $pos);
63                break;
64            case DOKU_LEXER_EXIT:
65                $handler->addCall('preformatted_end', [], $pos);
66                /** @var PreformattedHandler $reWriter */
67                $reWriter = $handler->getCallWriter();
68                $handler->setCallWriter($reWriter->process());
69                break;
70            case DOKU_LEXER_MATCHED:
71                $handler->addCall('preformatted_newline', [], $pos);
72                break;
73            case DOKU_LEXER_UNMATCHED:
74                $handler->addCall('preformatted_content', [$match], $pos);
75                break;
76        }
77
78        return true;
79    }
80}
81