Path: blob/master/src/infrastructure/markup/blockrule/PhutilRemarkupCodeBlockRule.php
12241 views
<?php12final class PhutilRemarkupCodeBlockRule extends PhutilRemarkupBlockRule {34public function getMatchingLineCount(array $lines, $cursor) {5$num_lines = 0;6$match_ticks = null;7if (preg_match('/^(\s{2,}).+/', $lines[$cursor])) {8$match_ticks = false;9} else if (preg_match('/^\s*(```)/', $lines[$cursor])) {10$match_ticks = true;11} else {12return $num_lines;13}1415$num_lines++;1617if ($match_ticks &&18preg_match('/^\s*(```)(.*)(```)\s*$/', $lines[$cursor])) {19return $num_lines;20}2122$cursor++;2324while (isset($lines[$cursor])) {25if ($match_ticks) {26if (preg_match('/```\s*$/', $lines[$cursor])) {27$num_lines++;28break;29}30$num_lines++;31} else {32if (strlen(trim($lines[$cursor]))) {33if (!preg_match('/^\s{2,}/', $lines[$cursor])) {34break;35}36}37$num_lines++;38}39$cursor++;40}4142return $num_lines;43}4445public function markupText($text, $children) {46if (preg_match('/^\s*```/', $text)) {47// If this is a ```-style block, trim off the backticks and any leading48// blank line.49$text = preg_replace('/^\s*```(\s*\n)?/', '', $text);50$text = preg_replace('/```\s*$/', '', $text);51}5253$lines = explode("\n", $text);54while ($lines && !strlen(last($lines))) {55unset($lines[last_key($lines)]);56}5758$options = array(59'counterexample' => false,60'lang' => null,61'name' => null,62'lines' => null,63);6465$parser = new PhutilSimpleOptions();66$custom = $parser->parse(head($lines));67if ($custom) {68$valid = true;69foreach ($custom as $key => $value) {70if (!array_key_exists($key, $options)) {71$valid = false;72break;73}74}75if ($valid) {76array_shift($lines);77$options = $custom + $options;78}79}8081// Normalize the text back to a 0-level indent.82$min_indent = 80;83foreach ($lines as $line) {84for ($ii = 0; $ii < strlen($line); $ii++) {85if ($line[$ii] != ' ') {86$min_indent = min($ii, $min_indent);87break;88}89}90}9192$text = implode("\n", $lines);93if ($min_indent) {94$indent_string = str_repeat(' ', $min_indent);95$text = preg_replace('/^'.$indent_string.'/m', '', $text);96}9798if ($this->getEngine()->isTextMode()) {99$out = array();100101$header = array();102if ($options['counterexample']) {103$header[] = 'counterexample';104}105if ($options['name'] != '') {106$header[] = 'name='.$options['name'];107}108if ($header) {109$out[] = implode(', ', $header);110}111112$text = preg_replace('/^/m', ' ', $text);113$out[] = $text;114115return implode("\n", $out);116}117118if (empty($options['lang'])) {119// If the user hasn't specified "lang=..." explicitly, try to guess the120// language. If we fail, fall back to configured defaults.121$lang = PhutilLanguageGuesser::guessLanguage($text);122if (!$lang) {123$lang = nonempty(124$this->getEngine()->getConfig('phutil.codeblock.language-default'),125'text');126}127$options['lang'] = $lang;128}129130$code_body = $this->highlightSource($text, $options);131132$name_header = null;133$block_style = null;134if ($this->getEngine()->isHTMLMailMode()) {135$map = $this->getEngine()->getConfig('phutil.codeblock.style-map');136137if ($map) {138$raw_body = id(new PhutilPygmentizeParser())139->setMap($map)140->parse((string)$code_body);141$code_body = phutil_safe_html($raw_body);142}143144$style_rules = array(145'padding: 6px 12px;',146'font-size: 13px;',147'font-weight: bold;',148'display: inline-block;',149'border-top-left-radius: 3px;',150'border-top-right-radius: 3px;',151'color: rgba(0,0,0,.75);',152);153154if ($options['counterexample']) {155$style_rules[] = 'background: #f7e6e6';156} else {157$style_rules[] = 'background: rgba(71, 87, 120, 0.08);';158}159160$header_attributes = array(161'style' => implode(' ', $style_rules),162);163164$block_style = 'margin: 12px 0;';165} else {166$header_attributes = array(167'class' => 'remarkup-code-header',168);169}170171if ($options['name']) {172$name_header = phutil_tag(173'div',174$header_attributes,175$options['name']);176}177178$class = 'remarkup-code-block';179if ($options['counterexample']) {180$class = 'remarkup-code-block code-block-counterexample';181}182183$attributes = array(184'class' => $class,185'style' => $block_style,186'data-code-lang' => $options['lang'],187'data-sigil' => 'remarkup-code-block',188);189190return phutil_tag(191'div',192$attributes,193array($name_header, $code_body));194}195196private function highlightSource($text, array $options) {197if ($options['counterexample']) {198$aux_class = ' remarkup-counterexample';199} else {200$aux_class = null;201}202203$aux_style = null;204205if ($this->getEngine()->isHTMLMailMode()) {206$aux_style = array(207'font: 11px/15px "Menlo", "Consolas", "Monaco", monospace;',208'padding: 12px;',209'margin: 0;',210);211212if ($options['counterexample']) {213$aux_style[] = 'background: #f7e6e6;';214} else {215$aux_style[] = 'background: rgba(71, 87, 120, 0.08);';216}217218$aux_style = implode(' ', $aux_style);219}220221if ($options['lines']) {222// Put a minimum size on this because the scrollbar is otherwise223// unusable.224$height = max(6, (int)$options['lines']);225$aux_style = $aux_style226.' '227.'max-height: '228.(2 * $height)229.'em; overflow: auto;';230}231232$engine = $this->getEngine()->getConfig('syntax-highlighter.engine');233if (!$engine) {234$engine = 'PhutilDefaultSyntaxHighlighterEngine';235}236$engine = newv($engine, array());237$engine->setConfig(238'pygments.enabled',239$this->getEngine()->getConfig('pygments.enabled'));240return phutil_tag(241'pre',242array(243'class' => 'remarkup-code'.$aux_class,244'style' => $aux_style,245),246PhutilSafeHTML::applyFunction(247'rtrim',248$engine->highlightSource($options['lang'], $text)));249}250251}252253254