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