22 class SyntaxHighlight {
25 const HIGHLIGHT_MAX_LINES = 1000;
28 const HIGHLIGHT_MAX_BYTES = 102400;
31 const HIGHLIGHT_CSS_CLASS =
'mw-highlight';
34 const CACHE_VERSION = 2;
38 'text/javascript' =>
'javascript',
39 'application/json' =>
'javascript',
50 static $lexers =
null;
52 if (
$lang ===
null ) {
57 $lexers = require __DIR__ .
'/../SyntaxHighlight.lexers.php';
60 $lexer = strtolower(
$lang );
62 if ( isset( $lexers[$lexer] ) ) {
70 if ( isset( $geshi2pygments[$lexer] ) ) {
71 $lexer = $geshi2pygments[$lexer];
72 if ( in_array( $lexer, $lexers ) ) {
86 foreach ( [
'source',
'syntaxhighlight' ] as $tag ) {
87 $parser->setHook( $tag, [
'SyntaxHighlight',
'parserHook' ] );
102 $out = $parser->mStripState->unstripNoWiki( $text );
105 $out = preg_replace(
'/^\n+/',
'', rtrim( $out ) );
108 if ( isset(
$args[
'enclose'] ) ) {
109 if (
$args[
'enclose'] ===
'none' ) {
110 $args[
'inline'] =
true;
112 unset(
$args[
'enclose'] );
115 $lexer =
$args[
'lang'] ??
'';
117 $result = self::highlight( $out, $lexer,
$args );
118 if ( !$result->isGood() ) {
119 $parser->addTrackingCategory(
'syntaxhighlight-error-category' );
121 $out = $result->getValue();
124 $htmlAttribs = Sanitizer::validateAttributes(
$args, [
'style',
'class',
'id',
'dir' ] );
125 if ( !isset( $htmlAttribs[
'class'] ) ) {
126 $htmlAttribs[
'class'] = self::HIGHLIGHT_CSS_CLASS;
128 $htmlAttribs[
'class'] .=
' ' . self::HIGHLIGHT_CSS_CLASS;
130 if ( !( isset( $htmlAttribs[
'dir'] ) && $htmlAttribs[
'dir'] ===
'rtl' ) ) {
131 $htmlAttribs[
'dir'] =
'ltr';
134 if ( isset(
$args[
'inline'] ) ) {
137 $out = str_replace(
"\n",
' ', $out );
138 $out = Html::rawElement(
'code', $htmlAttribs, $out );
142 $htmlAttribs[
'class'] .=
' ' .
'mw-content-' . $htmlAttribs[
'dir'];
149 if ( preg_match(
'/^<div class="?mw-highlight"?>(.*)<\/div>$/s', trim( $out ), $m ) ) {
150 $out = trim( $m[1] );
152 throw new MWException(
'Unexpected output from Pygments encountered' );
158 $marker = $parser::MARKER_PREFIX .
'-syntaxhighlightinner-' .
159 sprintf(
'%08X', $parser->mMarkerIndex++ ) . $parser::MARKER_SUFFIX;
160 $parser->mStripState->addNoWiki( $marker, $out );
162 $out = Html::openElement(
'div', $htmlAttribs ) .
164 Html::closeElement(
'div' );
170 $parser->getOutput()->addModuleStyles(
'ext.pygments' );
178 public static function getPygmentizePath() {
179 global $wgPygmentizePath;
182 if ( $wgPygmentizePath ===
false ) {
183 $wgPygmentizePath = __DIR__ .
'/../pygments/pygmentize';
186 return $wgPygmentizePath;
193 private static function plainCodeWrap( $code, $inline ) {
195 return htmlspecialchars( $code, ENT_NOQUOTES );
198 return Html::rawElement(
200 [
'class' => self::HIGHLIGHT_CSS_CLASS ],
201 Html::element(
'pre', [], $code )
223 public static function highlight( $code,
$lang =
null,
$args = [] ) {
227 if ( $lexer ===
null &&
$lang !==
null ) {
228 $status->warning(
'syntaxhighlight-error-unknown-language',
$lang );
232 if ( $code ===
'' ) {
237 $length = strlen( $code );
238 if ( strlen( $code ) > self::HIGHLIGHT_MAX_BYTES ) {
242 'syntaxhighlight-error-exceeds-size-limit',
244 self::HIGHLIGHT_MAX_BYTES
246 } elseif ( Shell::isDisabled() ) {
249 $status->warning(
'syntaxhighlight-error-pygments-invocation-failure' );
251 'MediaWiki determined that it cannot invoke Pygments. ' .
252 'As a result, SyntaxHighlight_GeSHi will not perform any syntax highlighting. ' .
253 'See the debug log for details: ' .
254 'https://www.mediawiki.org/wiki/Manual:$wgDebugLogFile'
258 $inline = isset(
$args[
'inline'] );
261 $code = trim( $code );
264 if ( $lexer ===
null ) {
266 $status->value = self::plainCodeWrap( $code, $inline );
271 'cssclass' => self::HIGHLIGHT_CSS_CLASS,
272 'encoding' =>
'utf-8',
276 if ( isset(
$args[
'line'] ) ) {
277 $options[
'linenos'] =
'inline';
280 if ( $lexer ===
'php' && strpos( $code,
'<?php' ) ===
false ) {
281 $options[
'startinline'] = 1;
285 if ( isset(
$args[
'highlight'] ) ) {
288 $options[
'hl_lines'] = implode(
' ',
$lines );
293 if ( isset(
$args[
'start'] ) && ctype_digit(
$args[
'start'] ) ) {
294 $options[
'linenostart'] = (int)
$args[
'start'];
298 $options[
'nowrap'] = 1;
301 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
306 function ( $oldValue, &$ttl ) use ( $code, $lexer, $options, &$error ) {
308 foreach ( $options as $k => $v ) {
309 $optionPairs[] =
"{$k}={$v}";
311 $result = Shell::command(
312 self::getPygmentizePath(),
315 '-O', implode(
',', $optionPairs )
318 ->restrict( Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK )
321 if ( $result->getExitCode() != 0 ) {
322 $ttl = WANObjectCache::TTL_UNCACHEABLE;
323 $error = $result->getStderr();
327 return $result->getStdout();
331 if ( $error !==
null ||
$output ===
null ) {
332 $status->warning(
'syntaxhighlight-error-pygments-invocation-failure' );
333 wfWarn(
'Failed to invoke Pygments: ' . $error );
335 $output = self::plainCodeWrap( $code, $inline );
360 return md5(
"{$code}|{$lexer}|{$optionString}|" . self::CACHE_VERSION );
374 $values = array_map(
'trim', explode(
',', $lineSpec ) );
375 foreach ( $values as $value ) {
376 if ( ctype_digit( $value ) ) {
378 } elseif ( strpos( $value,
'-' ) !==
false ) {
379 list( $start, $end ) = array_map(
'trim', explode(
'-', $value ) );
381 for ( $i = intval( $start ); $i <= $end; $i++ ) {
386 if ( count(
$lines ) > self::HIGHLIGHT_MAX_LINES ) {
387 $lines = array_slice(
$lines, 0, self::HIGHLIGHT_MAX_LINES );
406 return ctype_digit( $start ) &&
407 ctype_digit( $end ) &&
410 $end - $start < self::HIGHLIGHT_MAX_LINES;
431 if ( !$generateHtml ) {
438 $models = $extension->getAttribute(
'SyntaxHighlightModels' );
440 if ( !isset( $models[$model] ) ) {
444 $lexer = $models[$model];
459 $status = self::highlight( $text, $lexer );
465 $output->addModuleStyles(
'ext.pygments' );
466 $output->setText(
'<div dir="ltr">' . $out .
'</div>' );
488 $status = self::highlight( $text, $lexer );
494 if ( preg_match(
'/^<pre([^>]*)>/i', $out, $m ) ) {
495 $attrs = Sanitizer::decodeTagAttributes( $m[1] );
496 $attrs[
'class'] .=
' api-pretty-content';
497 $encodedAttrs = Sanitizer::safeEncodeTagAttributes( $attrs );
498 $out =
'<pre' . $encodedAttrs .
'>' . substr( $out, strlen( $m[0] ) );
501 $output->addModuleStyles(
'ext.pygments' );
502 $output->addHTML(
'<div dir="ltr">' . $out .
'</div>' );
520 'class' => ResourceLoaderSyntaxHighlightVisualEditorModule::class,
521 'localBasePath' => __DIR__ .
'/../modules',
522 'remoteExtPath' =>
'SyntaxHighlight_GeSHi/modules',
524 've-syntaxhighlight/ve.dm.MWSyntaxHighlightNode.js',
525 've-syntaxhighlight/ve.dm.MWBlockSyntaxHighlightNode.js',
526 've-syntaxhighlight/ve.dm.MWInlineSyntaxHighlightNode.js',
527 've-syntaxhighlight/ve.ce.MWSyntaxHighlightNode.js',
528 've-syntaxhighlight/ve.ce.MWBlockSyntaxHighlightNode.js',
529 've-syntaxhighlight/ve.ce.MWInlineSyntaxHighlightNode.js',
530 've-syntaxhighlight/ve.ui.MWSyntaxHighlightWindow.js',
531 've-syntaxhighlight/ve.ui.MWSyntaxHighlightDialog.js',
532 've-syntaxhighlight/ve.ui.MWSyntaxHighlightDialogTool.js',
533 've-syntaxhighlight/ve.ui.MWSyntaxHighlightInspector.js',
534 've-syntaxhighlight/ve.ui.MWSyntaxHighlightInspectorTool.js',
537 've-syntaxhighlight/ve.ce.MWSyntaxHighlightNode.css',
538 've-syntaxhighlight/ve.ui.MWSyntaxHighlightDialog.css',
539 've-syntaxhighlight/ve.ui.MWSyntaxHighlightInspector.css',
542 'ext.visualEditor.mwcore',
543 'oojs-ui.styles.icons-editing-advanced'
546 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-code',
547 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-language',
548 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-none',
549 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-showlines',
550 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-startingline',
551 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-title',
553 'targets' => [
'desktop',
'mobile' ],
563 return new GeSHi( self::highlight( $text,
$lang )->getValue() );
574 $geshi->parse_code();
578 class_alias( SyntaxHighlight::class,
'SyntaxHighlight_GeSHi' );