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