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