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'] ??
'';
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' );
179 global $wgPygmentizePath;
182 if ( $wgPygmentizePath ===
false ) {
183 $wgPygmentizePath = __DIR__ .
'/../pygments/pygmentize';
186 return $wgPygmentizePath;
195 return htmlspecialchars( $code, ENT_NOQUOTES );
198 return Html::rawElement(
200 [
'class' => self::HIGHLIGHT_CSS_CLASS ],
201 Html::element(
'pre', [], $code )
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 ) {
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();
303 $output =
$cache->getWithSetCallback(
304 $cache->makeGlobalKey(
'highlight', self::makeCacheKeyHash( $code, $lexer, $options ) ),
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 );
343 $output = trim( $output );
346 $status->value = $output;
359 $optionString = FormatJson::encode( $options,
false, FormatJson::ALL_OK );
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 ) );
380 if ( self::validHighlightRange( $start, $end ) ) {
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 ) {
437 $extension = ExtensionRegistry::getInstance();
438 $models = $extension->getAttribute(
'SyntaxHighlightModels' );
440 if ( !isset( $models[$model] ) ) {
444 $lexer = $models[$model];
447 $text = ContentHandler::getContentText(
$content );
456 $output =
$wgParser->parse( $text,
$title, $options,
true,
true, $revId );
460 if ( !$status->isOK() ) {
463 $out = $status->getValue();
466 $output->
setText(
'<div dir="ltr">' . $out .
'</div>' );
483 if ( !isset( self::$mimeLexers[$mime] ) ) {
487 $lexer = self::$mimeLexers[$mime];
489 if ( !$status->isOK() ) {
493 $out = $status->getValue();
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>' );
515 if ( !ExtensionRegistry::getInstance()->isLoaded(
'VisualEditor' ) ) {
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();
578class_alias( SyntaxHighlight::class,
'SyntaxHighlight_GeSHi' );
$wgTextModelsToParse
Determines which types of text are parsed as wikitext.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static parseHighlightLines( $lineSpec)
Take an input specifying a list of lines to highlight, returning a raw list of matching line numbers.
static prepare( $text, $lang)
Backward-compatibility shim for extensions.
static onContentGetParserOutput(Content $content, Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output)
Hook into Content::getParserOutput to provide syntax highlighting for script content.
static makeCacheKeyHash( $code, $lexer, $options)
Construct a cache key for the results of a Pygments invocation.
static onApiFormatHighlight(IContextSource $context, $text, $mime, $format)
Hook to provide syntax highlighting for API pretty-printed output.
static validHighlightRange( $start, $end)
Validate a provided input range.
static onResourceLoaderRegisterModules(&$resourceLoader)
Conditionally register resource loader modules that depends on the VisualEditor MediaWiki extension.
static buildHeadItem( $geshi)
Backward-compatibility shim for extensions.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
Set options of the Parser.
addModuleStyles( $modules)
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
setHook( $tag, callable $callback)
Create an HTML-style tag, e.g.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static getGeSHiToPygmentsMap()
static highlight( $code, $lang=null, $args=[])
Highlight a code-block using a particular lexer.
static plainCodeWrap( $code, $inline)
static onParserFirstCallInit(Parser &$parser)
Register parser hook.
static array $mimeLexers
Mapping of MIME-types to lexer names.
static getPygmentizePath()
static getLexer( $lang)
Get the Pygments lexer name for a particular language.
static parserHook( $text, $args, $parser)
Parser hook.
Content object implementation for representing flat text.
Represents a title within MediaWiki.
Base interface for content objects.
Interface for objects which can provide a MediaWiki context on request.
if(!isset( $args[0])) $lang