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