summaryrefslogtreecommitdiff
path: root/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php')
-rw-r--r--extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php707
1 files changed, 325 insertions, 382 deletions
diff --git a/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php b/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php
index 732f2195..9eed2763 100644
--- a/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php
+++ b/extensions/SyntaxHighlight_GeSHi/SyntaxHighlight_GeSHi.class.php
@@ -1,5 +1,4 @@
<?php
-
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,34 +16,76 @@
* http://www.gnu.org/copyleft/gpl.html
*/
+use Symfony\Component\Process\ProcessBuilder;
+
+// @codingStandardsIgnoreStart
class SyntaxHighlight_GeSHi {
- /**
- * Has GeSHi been initialised this session?
- */
- private static $initialised = false;
+// @codingStandardsIgnoreEnd
- /**
- * List of languages available to GeSHi
- * @var array
- */
- private static $languages = null;
+ /** @var const The maximum number of lines that may be selected for highlighting. **/
+ const HIGHLIGHT_MAX_LINES = 1000;
+
+ /** @var const Maximum input size for the highlighter (100 kB). **/
+ const HIGHLIGHT_MAX_BYTES = 102400;
+
+ /** @var const CSS class for syntax-highlighted code. **/
+ const HIGHLIGHT_CSS_CLASS = 'mw-highlight';
+
+ /** @var const Cache version. Increment whenever the HTML changes. */
+ const CACHE_VERSION = 1;
+
+ /** @var array Mapping of MIME-types to lexer names. **/
+ private static $mimeLexers = array(
+ 'text/javascript' => 'javascript',
+ 'application/json' => 'javascript',
+ 'text/xml' => 'xml',
+ );
+
+ public static function onSetup() {
+ global $wgPygmentizePath;
+
+ // If $wgPygmentizePath is unset, use the bundled copy.
+ if ( $wgPygmentizePath === false ) {
+ $wgPygmentizePath = __DIR__ . '/pygments/pygmentize';
+ }
+ if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
+ require_once __DIR__ . '/vendor/autoload.php';
+ }
+ }
/**
- * Executed after processing extension.json
+ * Get the Pygments lexer name for a particular language.
+ *
+ * @param string $lang Language name.
+ * @return string|null Lexer name, or null if no matching lexer.
*/
- public static function registerExtension() {
- global $wgVersion;
- if ( version_compare( $wgVersion, '1.25', '<' ) ) {
- die( 'This version of SyntaxHighlight GeSHi requires MediaWiki 1.25' );
+ private static function getLexer( $lang ) {
+ static $lexers = null;
+
+ if ( $lang === null ) {
+ return null;
}
- global $wgGeSHiSupportedLanguages;
- if ( !$wgGeSHiSupportedLanguages ) {
- // If not set already, load it (@see ExtensionRegistry::exportExtractedData)
- require_once __DIR__ . '/SyntaxHighlight_GeSHi.langs.php';
+ if ( !$lexers ) {
+ $lexers = require __DIR__ . '/SyntaxHighlight_GeSHi.lexers.php';
}
- // @fixme we shouldn't be loading this on ever request
- require_once __DIR__ . '/geshi/geshi.php';
+
+ $lexer = strtolower( $lang );
+
+ if ( in_array( $lexer, $lexers ) ) {
+ return $lexer;
+ }
+
+ // Check if this is a GeSHi lexer name for which there exists
+ // a compatible Pygments lexer with a different name.
+ if ( isset( GeSHi::$compatibleLexers[$lexer] ) ) {
+ $lexer = GeSHi::$compatibleLexers[$lexer];
+ if ( in_array( $lexer, $lexers ) ) {
+ return $lexer;
+ }
+ }
+
+ return null;
}
/**
@@ -53,10 +94,10 @@ class SyntaxHighlight_GeSHi {
* @param $parser Parser
* @return bool
*/
- public static function configureParser( &$parser ) {
- $parser->setHook( 'source', array( 'SyntaxHighlight_GeSHi', 'parserHook' ) );
- $parser->setHook( 'syntaxhighlight', array( 'SyntaxHighlight_GeSHi', 'parserHook' ) );
- return true;
+ public static function onParserFirstCallInit( Parser &$parser ) {
+ foreach ( array( 'source', 'syntaxhighlight' ) as $tag ) {
+ $parser->setHook( $tag, array( 'SyntaxHighlight_GeSHi', 'parserHook' ) );
+ }
}
/**
@@ -68,114 +109,224 @@ class SyntaxHighlight_GeSHi {
* @return string
*/
public static function parserHook( $text, $args = array(), $parser ) {
- global $wgSyntaxHighlightDefaultLang, $wgUseTidy;
- self::initialise();
- $text = rtrim( $text );
+ global $wgUseTidy;
+
// Don't trim leading spaces away, just the linefeeds
- $text = preg_replace( '/^\n+/', '', $text );
+ $out = preg_replace( '/^\n+/', '', rtrim( $text ) );
+
+ // Convert deprecated attributes
+ if ( isset( $args['enclose'] ) ) {
+ if ( $args['enclose'] === 'none' ) {
+ $args['inline'] = true;
+ }
+ unset( $args['enclose'] );
+ }
+
+ $lexer = isset( $args['lang'] ) ? $args['lang'] : '';
+
+ $result = self::highlight( $out, $lexer, $args );
+ if ( !$result->isGood() ) {
+ $parser->addTrackingCategory( 'syntaxhighlight-error-category' );
+ }
+ $out = $result->getValue();
+
+ // HTML Tidy will convert tabs to spaces incorrectly (bug 30930).
+ // But the conversion from tab to space occurs while reading the input,
+ // before the conversion from &#9; to tab, so we can armor it that way.
+ if ( $wgUseTidy ) {
+ $out = str_replace( "\t", '&#9;', $out );
+ }
+
+ // Allow certain HTML attributes
+ $htmlAttribs = Sanitizer::validateAttributes( $args, array( 'style', 'class', 'id', 'dir' ) );
+ if ( !isset( $htmlAttribs['class'] ) ) {
+ $htmlAttribs['class'] = self::HIGHLIGHT_CSS_CLASS;
+ } else {
+ $htmlAttribs['class'] .= ' ' . self::HIGHLIGHT_CSS_CLASS;
+ }
+ if ( !( isset( $htmlAttribs['dir'] ) && $htmlAttribs['dir'] === 'rtl' ) ) {
+ $htmlAttribs['dir'] = 'ltr';
+ }
+
+ if ( isset( $args['inline'] ) ) {
+ // Enforce inlineness. Stray newlines may result in unexpected list and paragraph processing
+ // (also known as doBlockLevels()).
+ $out = str_replace( "\n", ' ', $out );
+ $out = Html::rawElement( 'code', $htmlAttribs, $out );
- // Validate language
- if ( isset( $args['lang'] ) && $args['lang'] ) {
- $lang = $args['lang'];
} else {
- // language is not specified. Check if default exists, if yes, use it.
- if ( !is_null( $wgSyntaxHighlightDefaultLang ) ) {
- $lang = $wgSyntaxHighlightDefaultLang;
+ // Not entirely sure what benefit this provides, but it was here already
+ $htmlAttribs['class'] .= ' ' . 'mw-content-' . $htmlAttribs['dir'];
+
+ // Unwrap Pygments output to provide our own wrapper. We can't just always use the 'nowrap'
+ // option (pass 'inline'), since it disables other useful things like line highlighting.
+ // Tolerate absence of quotes for Html::element() and wgWellFormedXml=false.
+ $m = array();
+ if ( preg_match( '/^<div class="?mw-highlight"?>(.*)<\/div>$/s', trim( $out ), $m ) ) {
+ $out = trim( $m[1] );
} else {
- $error = self::formatLanguageError( $text );
- return $error;
+ throw new MWException( 'Unexpected output from Pygments encountered' );
}
+
+ // Use 'nowiki' strip marker to prevent list processing (also known as doBlockLevels()).
+ // However, leave the wrapping <div/> outside to prevent <p/>-wrapping.
+ $marker = $parser->mUniqPrefix . '-syntaxhighlightinner-' .
+ sprintf( '%08X', $parser->mMarkerIndex++ ) . $parser::MARKER_SUFFIX;
+ $parser->mStripState->addNoWiki( $marker, $out );
+
+ $out = Html::openElement( 'div', $htmlAttribs ) .
+ $marker .
+ Html::closeElement( 'div' );
+ }
+
+ // Register CSS
+ $parser->getOutput()->addModuleStyles( 'ext.pygments' );
+
+ return $out;
+ }
+
+ /**
+ * Highlight a code-block using a particular lexer.
+ *
+ * @param string $code Code to highlight.
+ * @param string|null $lang Language name, or null to use plain markup.
+ * @param array $args Associative array of additional arguments.
+ * If it contains a 'line' key, the output will include line numbers.
+ * If it includes a 'highlight' key, the value will be parsed as a
+ * comma-separated list of lines and line-ranges to highlight.
+ * If it contains a 'start' key, the value will be used as the line at which to
+ * start highlighting.
+ * If it contains a 'inline' key, the output will not be wrapped in `<div><pre/></div>`.
+ * @return Status Status object, with HTML representing the highlighted
+ * code as its value.
+ */
+ protected static function highlight( $code, $lang = null, $args = array() ) {
+ global $wgPygmentizePath;
+
+ $status = new Status;
+
+ $lexer = self::getLexer( $lang );
+ if ( $lexer === null && $lang !== null ) {
+ $status->warning( 'syntaxhighlight-error-unknown-language', $lang );
}
- $lang = strtolower( $lang );
- if ( !preg_match( '/^[a-z_0-9-]*$/', $lang ) ) {
- $error = self::formatLanguageError( $text );
- return $error;
+
+ $length = strlen( $code );
+ if ( strlen( $code ) > self::HIGHLIGHT_MAX_BYTES ) {
+ $status->warning( 'syntaxhighlight-error-exceeds-size-limit',
+ $length, self::HIGHLIGHT_MAX_BYTES );
+ $lexer = null;
+ }
+
+ if ( wfShellExecDisabled() !== false ) {
+ $status->warning( 'syntaxhighlight-error-pygments-invocation-failure' );
+ wfWarn(
+ 'MediaWiki determined that it cannot invoke Pygments. ' .
+ 'As a result, SyntaxHighlight_GeSHi will not perform any syntax highlighting. ' .
+ 'See the debug log for details: ' .
+ 'https://www.mediawiki.org/wiki/Manual:$wgDebugLogFile'
+ );
+ $lexer = null;
}
- $geshi = self::prepare( $text, $lang );
- if ( !$geshi instanceof GeSHi ) {
- $error = self::formatLanguageError( $text );
- return $error;
+
+ $inline = isset( $args['inline'] );
+
+ if ( $lexer === null ) {
+ if ( $inline ) {
+ $status->value = htmlspecialchars( trim( $code ), ENT_NOQUOTES );
+ } else {
+ $pre = Html::element( 'pre', array(), $code );
+ $status->value = Html::rawElement( 'div', array( 'class' => self::HIGHLIGHT_CSS_CLASS ), $pre );
+ }
+ return $status;
}
- $enclose = self::getEncloseType( $args );
+ $options = array(
+ 'cssclass' => self::HIGHLIGHT_CSS_CLASS,
+ 'encoding' => 'utf-8',
+ );
// Line numbers
if ( isset( $args['line'] ) ) {
- $geshi->enable_line_numbers( GESHI_FANCY_LINE_NUMBERS );
+ $options['linenos'] = 'inline';
}
- // Highlighting specific lines
+
+ if ( $lexer === 'php' && strpos( $code, '<?php' ) === false ) {
+ $options['startinline'] = 1;
+ }
+
+ // Highlight specific lines
if ( isset( $args['highlight'] ) ) {
$lines = self::parseHighlightLines( $args['highlight'] );
if ( count( $lines ) ) {
- $geshi->highlight_lines_extra( $lines );
+ $options['hl_lines'] = implode( ' ', $lines );
}
}
+
// Starting line number
if ( isset( $args['start'] ) ) {
- $geshi->start_line_numbers_at( $args['start'] );
- }
- $geshi->set_header_type( $enclose );
- // Strict mode
- if ( isset( $args['strict'] ) ) {
- $geshi->enable_strict_mode();
- }
- // Format
- $out = $geshi->parse_code();
- if ( $geshi->error == GESHI_ERROR_NO_SUCH_LANG ) {
- // Common error :D
- $error = self::formatLanguageError( $text );
- return $error;
- }
- $err = $geshi->error();
- if ( $err ) {
- // Other unknown error!
- $error = self::formatError( $err );
- return $error;
- }
- // Armour for Parser::doBlockLevels()
- if ( $enclose === GESHI_HEADER_DIV ) {
- $out = str_replace( "\n", '', $out );
+ $options['linenostart'] = $args['start'];
}
- // HTML Tidy will convert tabs to spaces incorrectly (bug 30930).
- // But the conversion from tab to space occurs while reading the input,
- // before the conversion from &#9; to tab, so we can armor it that way.
- if ( $wgUseTidy ) {
- $out = str_replace( "\t", '&#9;', $out );
+
+ if ( $inline ) {
+ $options['nowrap'] = 1;
}
- // Register CSS
- $parser->getOutput()->addModuleStyles( array( "ext.geshi.language.$lang", 'ext.geshi.local' ) );
- $encloseTag = $enclose === GESHI_HEADER_NONE ? 'span' : 'div';
- $attribs = Sanitizer::validateTagAttributes( $args, $encloseTag );
+ $cache = wfGetMainCache();
+ $cacheKey = self::makeCacheKey( $code, $lexer, $options );
+ $output = $cache->get( $cacheKey );
- //lang is valid in HTML context, but also used on GeSHi
- unset( $attribs['lang'] );
+ if ( $output === false ) {
+ $optionPairs = array();
+ foreach ( $options as $k => $v ) {
+ $optionPairs[] = "{$k}={$v}";
+ }
+ $builder = new ProcessBuilder();
+ $builder->setPrefix( $wgPygmentizePath );
+ $process = $builder
+ ->add( '-l' )->add( $lexer )
+ ->add( '-f' )->add( 'html' )
+ ->add( '-O' )->add( implode( ',', $optionPairs ) )
+ ->getProcess();
+
+ $process->setInput( $code );
+ $process->run();
+
+ if ( !$process->isSuccessful() ) {
+ $status->warning( 'syntaxhighlight-error-pygments-invocation-failure' );
+ wfWarn( 'Failed to invoke Pygments: ' . $process->getErrorOutput() );
+ $status->value = self::highlight( $code, null, $args )->getValue();
+ return $status;
+ }
- if ( $enclose === GESHI_HEADER_NONE ) {
- $attribs = self::addAttribute( $attribs, 'class', 'mw-geshi ' . $lang . ' source-' . $lang );
- } else {
- // Default dir="ltr" (but allow dir="rtl", although unsure if needed)
- $attribs['dir'] = isset( $attribs['dir'] ) && $attribs['dir'] === 'rtl' ? 'rtl' : 'ltr';
- $attribs = self::addAttribute( $attribs, 'class', 'mw-geshi mw-code mw-content-' . $attribs['dir'] );
+ $output = $process->getOutput();
+ $cache->set( $cacheKey, $output );
}
- $out = Html::rawElement( $encloseTag, $attribs, $out );
- return $out;
+ if ( $inline ) {
+ $output = trim( $output );
+ }
+
+ $status->value = $output;
+ return $status;
+
}
/**
- * @param $attribs array
- * @param $name string
- * @param $value string
- * @return array
+ * Construct a cache key for the results of a Pygments invocation.
+ *
+ * @param string $code Code to be highlighted.
+ * @param string $lexer Lexer name.
+ * @param array $options Options array.
+ * @return string Cache key.
*/
- private static function addAttribute( $attribs, $name, $value ) {
- if ( isset( $attribs[$name] ) ) {
- $attribs[$name] = $value . ' ' . $attribs[$name];
+ private static function makeCacheKey( $code, $lexer, $options ) {
+ $optionString = FormatJson::encode( $options, false, FormatJson::ALL_OK );
+ $hash = md5( "{$code}|{$lexer}|{$optionString}|" . self::CACHE_VERSION );
+ if ( function_exists( 'wfGlobalCacheKey' ) ) {
+ return wfGlobalCacheKey( 'highlight', $hash );
} else {
- $attribs[$name] = $value;
+ return 'highlight:' . $hash;
}
- return $attribs;
}
/**
@@ -184,26 +335,26 @@ class SyntaxHighlight_GeSHi {
*
* Input is comma-separated list of lines or line ranges.
*
- * @param $arg string
- * @return array of ints
+ * @param string $lineSpec
+ * @return int[] Line numbers.
*/
- protected static function parseHighlightLines( $arg ) {
+ protected static function parseHighlightLines( $lineSpec ) {
$lines = array();
- $values = array_map( 'trim', explode( ',', $arg ) );
+ $values = array_map( 'trim', explode( ',', $lineSpec ) );
foreach ( $values as $value ) {
- if ( ctype_digit($value) ) {
- $lines[] = (int) $value;
+ if ( ctype_digit( $value ) ) {
+ $lines[] = (int)$value;
} elseif ( strpos( $value, '-' ) !== false ) {
list( $start, $end ) = array_map( 'trim', explode( '-', $value ) );
if ( self::validHighlightRange( $start, $end ) ) {
- for ($i = intval( $start ); $i <= $end; $i++ ) {
+ for ( $i = intval( $start ); $i <= $end; $i++ ) {
$lines[] = $i;
}
- } else {
- wfDebugLog( 'geshi', "Invalid range: $value\n" );
}
- } else {
- wfDebugLog( 'geshi', "Invalid line: $value\n" );
+ }
+ if ( count( $lines ) > self::HIGHLIGHT_MAX_LINES ) {
+ $lines = array_slice( $lines, 0, self::HIGHLIGHT_MAX_LINES );
+ break;
}
}
return $lines;
@@ -221,31 +372,12 @@ class SyntaxHighlight_GeSHi {
// to DoS the system by asking for a huge range.
// Impose an arbitrary limit on the number of lines in a
// given range to reduce the impact.
- $arbitrarilyLargeConstant = 10000;
return
- ctype_digit($start) &&
- ctype_digit($end) &&
+ ctype_digit( $start ) &&
+ ctype_digit( $end ) &&
$start > 0 &&
$start < $end &&
- $end - $start < $arbitrarilyLargeConstant;
- }
-
- /**
- * @param $args array
- * @return int
- */
- static function getEncloseType( $args ) {
- // "Enclose" parameter
- $enclose = GESHI_HEADER_PRE_VALID;
- if ( isset( $args['enclose'] ) ) {
- if ( $args['enclose'] === 'div' ) {
- $enclose = GESHI_HEADER_DIV;
- } elseif ( $args['enclose'] === 'none' ) {
- $enclose = GESHI_HEADER_NONE;
- }
- }
-
- return $enclose;
+ $end - $start < self::HIGHLIGHT_MAX_LINES;
}
/**
@@ -255,31 +387,29 @@ class SyntaxHighlight_GeSHi {
* @return bool
* @since MW 1.21
*/
- public static function renderHook( Content $content, Title $title,
- $revId, ParserOptions $options, $generateHtml, ParserOutput &$output
- ) {
+ public static function onContentGetParserOutput( Content $content, Title $title,
+ $revId, ParserOptions $options, $generateHtml, ParserOutput &$output ) {
- global $wgSyntaxHighlightModels, $wgUseSiteCss,
- $wgParser, $wgTextModelsToParse;
+ global $wgParser, $wgTextModelsToParse;
- $highlightModels = ExtensionRegistry::getInstance()->getAttribute( 'SyntaxHighlightModels' );
+ if ( !$generateHtml ) {
+ // Nothing special for us to do, let MediaWiki handle this.
+ return true;
+ }
// Determine the language
+ $extension = ExtensionRegistry::getInstance();
+ $models = $extension->getAttribute( 'SyntaxHighlightModels' );
$model = $content->getModel();
- if ( !isset( $highlightModels[$model] ) && !isset( $wgSyntaxHighlightModels[$model] ) ) {
+ if ( !isset( $models[$model] ) ) {
// We don't care about this model, carry on.
return true;
}
-
- if ( !$generateHtml ) {
- // Nothing special for us to do, let MediaWiki handle this.
- return true;
- }
+ $lexer = $models[$model];
// Hope that $wgSyntaxHighlightModels does not contain silly types.
$text = ContentHandler::getContentText( $content );
-
- if ( $text === null || $text === false ) {
+ if ( !$text ) {
// Oops! Non-text content? Let MediaWiki handle this.
return true;
}
@@ -290,35 +420,17 @@ class SyntaxHighlight_GeSHi {
$output = $wgParser->parse( $text, $title, $options, true, true, $revId );
}
- if ( isset( $highlightModels[$model] ) ) {
- $lang = $highlightModels[$model];
- } else {
- // TODO: Add deprecation warning after a while?
- $lang = $wgSyntaxHighlightModels[$model];
+ $status = self::highlight( $text, $lexer );
+ if ( !$status->isOK() ) {
+ return true;
}
+ $out = $status->getValue();
+ $output->addModuleStyles( 'ext.pygments' );
+ $output->setText( '<div dir="ltr">' . $out . '</div>' );
- // Attempt to format
- $geshi = self::prepare( $text, $lang );
- if ( $geshi instanceof GeSHi ) {
-
- $out = $geshi->parse_code();
- if ( !$geshi->error() ) {
- // Done
- $output->addModuleStyles( "ext.geshi.language.$lang" );
- $output->setText( "<div dir=\"ltr\">{$out}</div>" );
-
- if ( $wgUseSiteCss ) {
- $output->addModuleStyles( 'ext.geshi.local' );
- }
-
- // Inform MediaWiki that we have parsed this page and it shouldn't mess with it.
- return false;
- }
- }
-
- // Bottle out
- return true;
+ // Inform MediaWiki that we have parsed this page and it shouldn't mess with it.
+ return false;
}
/**
@@ -330,232 +442,63 @@ class SyntaxHighlight_GeSHi {
* @param string $format
* @since MW 1.24
*/
- public static function apiFormatHighlight( IContextSource $context, $text, $mime, $format ) {
- switch ( $mime ) {
- case 'text/javascript':
- case 'application/json':
- $lang = 'javascript';
- break;
-
- case 'text/xml':
- $lang = 'xml';
- break;
-
- default:
- // Don't know how to handle this
- return true;
+ public static function onApiFormatHighlight( IContextSource $context, $text, $mime, $format ) {
+ if ( !isset( self::$mimeLexers[$mime] ) ) {
+ return true;
}
- $geshi = self::prepare( $text, $lang );
- if ( $geshi instanceof GeSHi ) {
- $out = $geshi->parse_code();
- if ( !$geshi->error() ) {
- if ( preg_match( '/^<pre([^>]*)>/i', $out, $m ) ) {
- $attrs = Sanitizer::decodeTagAttributes( $m[1] );
- $attrs['class'] .= ' api-pretty-content';
- $out = '<pre' . Sanitizer::safeEncodeTagAttributes( $attrs ) . '>' .
- substr( $out, strlen( $m[0] ) );
- }
- $output = $context->getOutput();
- $output->addModuleStyles( array( "ext.geshi.language.$lang", 'ext.geshi.local' ) );
- $output->addHTML( "<div dir=\"ltr\">{$out}</div>" );
-
- // Inform MediaWiki that we have parsed this page and it shouldn't mess with it.
- return false;
- }
+ $lexer = self::$mimeLexers[$mime];
+ $status = self::highlight( $text, $lexer );
+ if ( !$status->isOK() ) {
+ return true;
}
- // Bottle out
- return true;
- }
-
- /**
- * Initialise a GeSHi object to format some code, performing
- * common setup for all our uses of it
- *
- * @param string $text
- * @param string $lang
- * @return GeSHi
- */
- public static function prepare( $text, $lang ) {
-
- global $wgSyntaxHighlightKeywordLinks;
-
- self::initialise();
- $geshi = new GeSHi( $text, $lang );
- if ( $geshi->error() == GESHI_ERROR_NO_SUCH_LANG ) {
- return null;
- }
- $geshi->set_encoding( 'UTF-8' );
- $geshi->enable_classes();
- $geshi->set_overall_class( "source-$lang" );
- $geshi->enable_keyword_links( $wgSyntaxHighlightKeywordLinks );
-
- // If the source code is over 100 kB, disable higlighting of symbols.
- // If over 200 kB, disable highlighting of strings too.
- $bytes = strlen( $text );
- if ( $bytes > 102400 ) {
- $geshi->set_symbols_highlighting( false );
- if ( $bytes > 204800 ) {
- $geshi->set_strings_highlighting( false );
- }
+ $out = $status->getValue();
+ if ( preg_match( '/^<pre([^>]*)>/i', $out, $m ) ) {
+ $attrs = Sanitizer::decodeTagAttributes( $m[1] );
+ $attrs['class'] .= ' api-pretty-content';
+ $encodedAttrs = Sanitizer::safeEncodeTagAttributes( $attrs );
+ $out = '<pre' . $encodedAttrs. '>' . substr( $out, strlen( $m[0] ) );
}
+ $output = $context->getOutput();
+ $output->addModuleStyles( 'ext.pygments' );
+ $output->addHTML( '<div dir="ltr">' . $out . '</div>' );
- /**
- * GeSHi comes by default with a font-family set to monospace, which
- * causes the font-size to be smaller than one would expect.
- * We append a CSS hack to the default GeSHi styles: specifying 'monospace'
- * twice "resets" the browser font-size specified for monospace.
- *
- * The hack is documented in MediaWiki core under
- * docs/uidesign/monospace.html and in bug 33496.
- */
- // Preserve default since we don't want to override the other style
- // properties set by geshi (padding, font-size, vertical-align etc.)
- $geshi->set_code_style(
- 'font-family: monospace, monospace;',
- /* preserve defaults = */ true
- );
-
- // No need to preserve default (which is just "font-family: monospace;")
- // outputting both is unnecessary
- $geshi->set_overall_style(
- 'font-family: monospace, monospace;',
- /* preserve defaults = */ false
- );
-
- return $geshi;
+ // Inform MediaWiki that we have parsed this page and it shouldn't mess with it.
+ return false;
}
/**
- * Prepare a CSS snippet suitable for use as a ParserOutput/OutputPage
- * head item.
- *
- * Not used anymore, kept for backwards-compatibility with other extensions.
+ * Reject parser cache values that are for GeSHi since those
+ * ResourceLoader modules no longer exist
*
- * @deprecated
- * @param GeSHi $geshi
- * @return string
- */
- public static function buildHeadItem( $geshi ) {
- wfDeprecated( __METHOD__ );
- $css = array();
- $css[] = '<style type="text/css">/*<![CDATA[*/';
- $css[] = self::getCSS( $geshi );
- $css[] = '/*]]>*/';
- $css[] = '</style>';
- return implode( "\n", $css );
- }
-
- /**
- * Get the complete CSS code necessary to display styles for given GeSHi instance.
- *
- * @param GeSHi $geshi
- * @return string
- */
- public static function getCSS( $geshi ) {
- $lang = $geshi->language;
- $css = array();
- $css[] = ".source-$lang {line-height: normal;}";
- $css[] = ".source-$lang li, .source-$lang pre {";
- $css[] = "\tline-height: normal; border: 0px none white;";
- $css[] = "}";
- $css[] = $geshi->get_stylesheet( /*$economy_mode*/ false );
- return implode( "\n", $css );
- }
-
- /**
- * Format an 'unknown language' error message and append formatted
- * plain text to it.
- *
- * @param string $text
- * @return string HTML fragment
- */
- private static function formatLanguageError( $text ) {
- $msg = wfMessage( 'syntaxhighlight-err-language' )->inContentLanguage()->escaped();
- $error = self::formatError( $msg, $text );
- return $error . '<pre>' . htmlspecialchars( $text ) . '</pre>';
- }
-
- /**
- * Format an error message
- *
- * @param string $error
- * @return string
- */
- private static function formatError( $error = '' ) {
- $html = '';
- if ( $error ) {
- $html .= "<p>{$error}</p>";
- }
- $html .= '<p>' . wfMessage( 'syntaxhighlight-specify')->inContentLanguage()->escaped()
- . ' <samp>&lt;source lang=&quot;html4strict&quot;&gt;...&lt;/source&gt;</samp></p>'
- . '<p>' . wfMessage( 'syntaxhighlight-supported' )->inContentLanguage()->escaped()
- . '</p>' . self::formatLanguages();
- return "<div style=\"border: solid red 1px; padding: .5em;\">{$html}</div>";
- }
-
- /**
- * Format the list of supported languages
- *
- * @return string
- */
- private static function formatLanguages() {
- $langs = self::getSupportedLanguages();
- $list = array();
- if ( count( $langs ) > 0 ) {
- foreach ( $langs as $lang ) {
- $list[] = '<samp>' . htmlspecialchars( $lang ) . '</samp>';
- }
- return '<p class="mw-collapsible mw-collapsed" style="padding: 0em 1em;">' . implode( ', ', $list ) . '</p><br style="clear: all"/>';
- } else {
- return '<p>' . wfMessage( 'syntaxhighlight-err-loading' )->inContentLanguage()->escaped() . '</p>';
- }
- }
-
- /**
- * Get the list of supported languages
- *
- * @return array
- */
- private static function getSupportedLanguages() {
- global $wgGeSHiSupportedLanguages;
- self::initialise();
- return $wgGeSHiSupportedLanguages;
- }
-
- /**
- * Initialise messages and ensure the GeSHi class is loaded
+ * @param ParserOutput $parserOutput
+ * @param WikiPage|Article $page
+ * @param ParserOptions $popts
* @return bool
*/
- private static function initialise() {
- if ( !self::$initialised ) {
- if ( !class_exists( 'GeSHi' ) ) {
- require ( dirname( __FILE__ ) . '/geshi/geshi.php' );
+ public static function onRejectParserCacheValue(
+ ParserOutput $parserOutput, $page, ParserOptions $popts
+ ) {
+ foreach ( $parserOutput->getModuleStyles() as $module ) {
+ if ( strpos( $module, 'ext.geshi.' ) === 0 ) {
+ $page->getTitle()->purgeSquid();
+ return false;
}
- self::$initialised = true;
}
return true;
}
- /**
- * Register a ResourceLoader module providing styles for each supported language.
- *
- * @param ResourceLoader $resourceLoader
- * @return bool true
- */
- public static function resourceLoaderRegisterModules( &$resourceLoader ) {
- $modules = array();
-
- foreach ( self::getSupportedLanguages() as $lang ) {
- $modules["ext.geshi.language.$lang" ] = array(
- 'class' => 'ResourceLoaderGeSHiModule',
- 'lang' => $lang,
- );
- }
-
- $resourceLoader->register( $modules );
+ /** Backward-compatibility shim for extensions. */
+ public static function prepare( $text, $lang ) {
+ wfDeprecated( __METHOD__ );
+ return new GeSHi( self::highlight( $text, $lang )->getValue() );
+ }
- return true;
+ /** Backward-compatibility shim for extensions. */
+ public static function buildHeadItem( $geshi ) {
+ wfDeprecated( __METHOD__ );
+ $geshi->parse_code();
+ return '';
}
}