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 $parser->setHook(
'source', [
'SyntaxHighlight',
'parserHookSource' ] );
87 $parser->setHook(
'syntaxhighlight', [
'SyntaxHighlight',
'parserHook' ] );
100 $parser->addTrackingCategory(
'syntaxhighlight-source-category' );
115 $out = $parser->mStripState->unstripNoWiki( $text );
118 $out = preg_replace(
'/^\n+/',
'', rtrim( $out ) );
121 if ( isset(
$args[
'enclose'] ) ) {
122 if (
$args[
'enclose'] ===
'none' ) {
123 $args[
'inline'] =
true;
125 unset(
$args[
'enclose'] );
126 $parser->addTrackingCategory(
'syntaxhighlight-enclose-category' );
129 $lexer =
$args[
'lang'] ??
'';
132 if ( !$result->isGood() ) {
133 $parser->addTrackingCategory(
'syntaxhighlight-error-category' );
135 $out = $result->getValue();
138 $htmlAttribs = Sanitizer::validateAttributes(
139 $args, array_flip( [
'style',
'class',
'id',
'dir' ] )
141 if ( !isset( $htmlAttribs[
'class'] ) ) {
142 $htmlAttribs[
'class'] = self::HIGHLIGHT_CSS_CLASS;
144 $htmlAttribs[
'class'] .=
' ' . self::HIGHLIGHT_CSS_CLASS;
147 if ( $lexer !==
null ) {
148 $htmlAttribs[
'class'] .=
' ' . self::HIGHLIGHT_CSS_CLASS .
'-lang-' . $lexer;
150 if ( !( isset( $htmlAttribs[
'dir'] ) && $htmlAttribs[
'dir'] ===
'rtl' ) ) {
151 $htmlAttribs[
'dir'] =
'ltr';
153 '@phan-var array{class:string,dir:string} $htmlAttribs';
155 if ( isset(
$args[
'inline'] ) ) {
158 $out = str_replace(
"\n",
' ', $out );
159 $out = Html::rawElement(
'code', $htmlAttribs, $out );
163 $htmlAttribs[
'class'] .=
' ' .
'mw-content-' . $htmlAttribs[
'dir'];
170 if ( preg_match(
'/^<div class="?mw-highlight"?>(.*)<\/div>$/s', trim( $out ), $m ) ) {
171 $out = trim( $m[1] );
173 throw new MWException(
'Unexpected output from Pygments encountered' );
179 $marker = $parser::MARKER_PREFIX .
'-syntaxhighlightinner-' .
180 sprintf(
'%08X', $parser->mMarkerIndex++ ) . $parser::MARKER_SUFFIX;
181 $parser->mStripState->addNoWiki( $marker, $out );
183 $out = Html::openElement(
'div', $htmlAttribs ) .
185 Html::closeElement(
'div' );
191 $parser->getOutput()->addModuleStyles(
'ext.pygments' );
200 global $wgPygmentizePath;
203 if ( $wgPygmentizePath ===
false ) {
204 $wgPygmentizePath = __DIR__ .
'/../pygments/pygmentize';
207 return $wgPygmentizePath;
217 return htmlspecialchars( $code, ENT_NOQUOTES );
220 return Html::rawElement(
222 [
'class' => self::HIGHLIGHT_CSS_CLASS ],
223 Html::element(
'pre', [], $code )
249 if ( $lexer ===
null &&
$lang !==
null ) {
250 $status->warning(
'syntaxhighlight-error-unknown-language',
$lang );
254 if ( $code ===
'' ) {
259 $length = strlen( $code );
260 if ( strlen( $code ) > self::HIGHLIGHT_MAX_BYTES ) {
264 'syntaxhighlight-error-exceeds-size-limit',
266 self::HIGHLIGHT_MAX_BYTES
268 } elseif ( Shell::isDisabled() ) {
271 $status->warning(
'syntaxhighlight-error-pygments-invocation-failure' );
273 'MediaWiki determined that it cannot invoke Pygments. ' .
274 'As a result, SyntaxHighlight_GeSHi will not perform any syntax highlighting. ' .
275 'See the debug log for details: ' .
276 'https://www.mediawiki.org/wiki/Manual:$wgDebugLogFile'
280 $inline = isset(
$args[
'inline'] );
283 $code = trim( $code );
286 if ( $lexer ===
null ) {
293 'cssclass' => self::HIGHLIGHT_CSS_CLASS,
294 'encoding' =>
'utf-8',
298 if ( isset(
$args[
'line'] ) ) {
299 $options[
'linenos'] =
'inline';
302 if ( $lexer ===
'php' && strpos( $code,
'<?php' ) ===
false ) {
303 $options[
'startinline'] = 1;
307 if ( isset(
$args[
'highlight'] ) ) {
310 $options[
'hl_lines'] = implode(
' ',
$lines );
315 if ( isset(
$args[
'start'] ) && ctype_digit(
$args[
'start'] ) ) {
316 $options[
'linenostart'] = (int)
$args[
'start'];
320 $options[
'nowrap'] = 1;
323 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
325 $output =
$cache->getWithSetCallback(
326 $cache->makeGlobalKey(
'highlight', self::makeCacheKeyHash( $code, $lexer, $options ) ),
328 function ( $oldValue, &$ttl ) use ( $code, $lexer, $options, &$error ) {
330 foreach ( $options as $k => $v ) {
331 $optionPairs[] =
"{$k}={$v}";
333 $result = Shell::command(
334 self::getPygmentizePath(),
337 '-O', implode(
',', $optionPairs )
340 ->restrict( Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK )
343 if ( $result->getExitCode() != 0 ) {
344 $ttl = WANObjectCache::TTL_UNCACHEABLE;
345 $error = $result->getStderr();
349 return $result->getStdout();
353 if ( $error !==
null || $output ===
null ) {
354 $status->warning(
'syntaxhighlight-error-pygments-invocation-failure' );
355 if ( $error !==
null ) {
356 wfWarn(
'Failed to invoke Pygments: ' . $error );
358 wfWarn(
'Invoking Pygments returned blank output with no error response' );
370 $output = trim( $output );
373 $status->value = $output;
386 $optionString = FormatJson::encode( $options,
false, FormatJson::ALL_OK );
387 return md5(
"{$code}|{$lexer}|{$optionString}|" . self::CACHE_VERSION );
401 $values = array_map(
'trim', explode(
',', $lineSpec ) );
402 foreach ( $values as $value ) {
403 if ( ctype_digit( $value ) ) {
405 } elseif ( strpos( $value,
'-' ) !==
false ) {
406 list( $start, $end ) = array_map(
'intval', explode(
'-', $value ) );
407 if ( self::validHighlightRange( $start, $end ) ) {
408 for ( $i = $start; $i <= $end; $i++ ) {
413 if ( count(
$lines ) > self::HIGHLIGHT_MAX_LINES ) {
414 $lines = array_slice(
$lines, 0, self::HIGHLIGHT_MAX_LINES );
435 $end - $start < self::HIGHLIGHT_MAX_LINES;
456 if ( !$generateHtml ) {
462 $extension = ExtensionRegistry::getInstance();
463 $models = $extension->getAttribute(
'SyntaxHighlightModels' );
465 if ( !isset( $models[$model] ) ) {
469 $lexer = $models[$model];
472 $text = ContentHandler::getContentText(
$content );
481 $output = MediaWikiServices::getInstance()->getParser()
482 ->parse( $text,
$title, $options,
true,
true, $revId );
486 if ( !$status->isOK() ) {
489 $out = $status->getValue();
492 $output->
setText(
'<div dir="ltr">' . $out .
'</div>' );
509 if ( !isset( self::$mimeLexers[
$mime] ) ) {
513 $lexer = self::$mimeLexers[
$mime];
515 if ( !$status->isOK() ) {
519 $out = $status->getValue();
520 if ( preg_match(
'/^<pre([^>]*)>/i', $out, $m ) ) {
521 $attrs = Sanitizer::decodeTagAttributes( $m[1] );
522 $attrs[
'class'] .=
' api-pretty-content';
523 $encodedAttrs = Sanitizer::safeEncodeTagAttributes( $attrs );
524 $out =
'<pre' . $encodedAttrs .
'>' . substr( $out, strlen( $m[0] ) );
527 $output->addModuleStyles(
'ext.pygments' );
528 $output->addHTML(
'<div dir="ltr">' . $out .
'</div>' );
541 if ( !ExtensionRegistry::getInstance()->isLoaded(
'VisualEditor' ) ) {
545 $resourceLoader->register(
'ext.geshi.visualEditor', [
546 'class' => ResourceLoaderSyntaxHighlightVisualEditorModule::class,
547 'localBasePath' => __DIR__ .
'/../modules',
548 'remoteExtPath' =>
'SyntaxHighlight_GeSHi/modules',
550 've-syntaxhighlight/ve.dm.MWSyntaxHighlightNode.js',
551 've-syntaxhighlight/ve.dm.MWBlockSyntaxHighlightNode.js',
552 've-syntaxhighlight/ve.dm.MWInlineSyntaxHighlightNode.js',
553 've-syntaxhighlight/ve.ce.MWSyntaxHighlightNode.js',
554 've-syntaxhighlight/ve.ce.MWBlockSyntaxHighlightNode.js',
555 've-syntaxhighlight/ve.ce.MWInlineSyntaxHighlightNode.js',
556 've-syntaxhighlight/ve.ui.MWSyntaxHighlightWindow.js',
557 've-syntaxhighlight/ve.ui.MWSyntaxHighlightDialog.js',
558 've-syntaxhighlight/ve.ui.MWSyntaxHighlightDialogTool.js',
559 've-syntaxhighlight/ve.ui.MWSyntaxHighlightInspector.js',
560 've-syntaxhighlight/ve.ui.MWSyntaxHighlightInspectorTool.js',
563 've-syntaxhighlight/ve.ce.MWSyntaxHighlightNode.css',
564 've-syntaxhighlight/ve.ui.MWSyntaxHighlightDialog.css',
565 've-syntaxhighlight/ve.ui.MWSyntaxHighlightInspector.css',
568 'ext.visualEditor.mwcore',
569 'oojs-ui.styles.icons-editing-advanced'
572 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-code',
573 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-language',
574 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-none',
575 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-showlines',
576 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-startingline',
577 'syntaxhighlight-visualeditor-mwsyntaxhighlightinspector-title',
579 'targets' => [
'desktop',
'mobile' ],
$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.
static parseHighlightLines( $lineSpec)
Take an input specifying a list of lines to highlight, returning a raw list of matching line numbers.
static onContentGetParserOutput(Content $content, Title $title, $revId, ParserOptions $options, $generateHtml, ParserOutput &$output)
Hook into Content::getParserOutput to provide syntax highlighting for script content.
static onResourceLoaderRegisterModules( $resourceLoader)
Conditionally register resource loader modules that depends on the VisualEditor MediaWiki extension.
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.
Set options of the Parser.
addModuleStyles( $modules)
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 parserHookSource( $text, $args, $parser)
Parser hook for <source> to add deprecated tracking category.
static plainCodeWrap( $code, $inline)
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 onParserFirstCallInit(Parser $parser)
Register parser hook.
static parserHook( $text, $args, $parser)
Parser hook for both <source> and <syntaxhighlight> logic.
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
if(!file_exists( $CREDITS)) $lines