]*)>/i', $out, $m ) ) { - $attrs = Sanitizer::decodeTagAttributes( $m[1] ); - $attrs['class'] .= ' api-pretty-content'; - $out = '' . - substr( $out, strlen( $m[0] ) ); - } - $output = $context->getOutput(); - $output->addModuleStyles( array( "ext.geshi.language.$lang", 'ext.geshi.local' ) ); - $output->addHTML( "{$out}" ); + $lexer = self::$mimeLexers[$mime]; + $out = self::highlight( $text, $lexer ); - // Inform MediaWiki that we have parsed this page and it shouldn't mess with it. - return false; - } + if ( !$out ) { + 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; + if ( preg_match( '/^]*)>/i', $out, $m ) ) { + $attrs = Sanitizer::decodeTagAttributes( $m[1] ); + $attrs['class'] .= ' api-pretty-content'; + $encodedAttrs = Sanitizer::safeEncodeTagAttributes( $attrs ); + $out = '' . substr( $out, strlen( $m[0] ) ); } - $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 ); - } - } - - /** - * 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 - ); + $output = $context->getOutput(); + $output->addModuleStyles( 'ext.pygments' ); + $output->addHTML( '' . $out . '' ); - 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. - * - * @deprecated - * @param GeSHi $geshi - * @return string - */ - public static function buildHeadItem( $geshi ) { + /** Backward-compatibility shim for extensions. */ + public static function prepare( $text, $lang ) { wfDeprecated( __METHOD__ ); - $css = array(); - $css[] = ''; - 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 . '' . htmlspecialchars( $text ) . ''; - } - - /** - * Format an error message - * - * @param string $error - * @return string - */ - private static function formatError( $error = '' ) { - $html = ''; - if ( $error ) { - $html .= "{$error}
"; - } - $html .= '' . wfMessage( 'syntaxhighlight-specify')->inContentLanguage()->escaped() - . ' <source lang="html4strict">...</source>
' - . '' . wfMessage( 'syntaxhighlight-supported' )->inContentLanguage()->escaped() - . '
' . self::formatLanguages(); - return "{$html}"; - } - - /** - * 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[] = '' . htmlspecialchars( $lang ) . ''; - } - return '' . implode( ', ', $list ) . '
'; - } else { - return '' . wfMessage( 'syntaxhighlight-err-loading' )->inContentLanguage()->escaped() . '
'; - } - } - - /** - * 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 - * @return bool - */ - private static function initialise() { - if ( !self::$initialised ) { - if ( !class_exists( 'GeSHi' ) ) { - require ( dirname( __FILE__ ) . '/geshi/geshi.php' ); - } - self::$initialised = true; - } - return true; + $html = self::highlight( $text, $lang ); + return new GeSHi( $html ); } - /** - * 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( - 'position' => 'top', - 'class' => 'ResourceLoaderGeSHiModule', - 'lang' => $lang, - ); - } - - $resourceLoader->register( $modules ); - - return true; + /** Backward-compatibility shim for extensions. */ + public static function buildHeadItem( $geshi ) { + wfDeprecated( __METHOD__ ); + $geshi->parse_code(); + return ''; } } diff --git a/SyntaxHighlight_GeSHi.compat.php b/SyntaxHighlight_GeSHi.compat.php new file mode 100644 index 0000000..9a89c87 --- /dev/null +++ b/SyntaxHighlight_GeSHi.compat.php @@ -0,0 +1,113 @@ + 'basic', + 'thinbasic' => 'basic', + 'sdlbasic' => 'basic', + 'purebasic' => 'basic', + 'mapbasic' => 'basic', + 'locobasic' => 'basic', + 'gwbasic' => 'basic', + 'freebasic' => 'basic', + 'basic4gl' => 'basic', + 'zxbasic' => 'basic', + 'gambas' => 'basic', + 'oobas' => 'basic', + 'bascomavr' => 'basic', + + // C / C++ + 'c_loadrunner' => 'c', + 'c_mac' => 'c', + 'c_winapi' => 'c', + 'upc' => 'c', + 'cpp-qt' => 'cpp', + 'cpp-winapi' => 'cpp', + 'urbi' => 'cpp', + + // HTML + 'html4strict' => 'html', + 'html5' => 'html', + + // JavaScript + 'jquery' => 'javascript', + 'ecmascript' => 'javascript', + + // Microsoft + 'vb' => 'vbnet', + 'asp' => 'aspx-vb', + 'visualfoxpro' => 'foxpro', + 'dos' => 'bat', + 'visualprolog' => 'prolog', + 'reg' => 'registry', + + // Miscellaneous + 'cadlisp' => 'lisp', + 'j' => 'objj', + 'java5' => 'java', + 'php-brief' => 'php', + 'povray' => 'pov', + 'pys60' => 'python', + 'rails' => 'ruby', + 'rpmspec' => 'spec', + 'rsplus' => 'splus', + + // ML + 'ocaml-brief' => 'ocaml', + 'standardml' => 'sml', + + // Modula 2 + 'modula3' => 'modula2', + 'oberon2' => 'modula2', + + // SQL + 'tsql' => 'sql', + 'plsql' => 'sql', + 'oracle11' => 'sql', + 'oracle8' => 'sql', + + // REXX + 'oorexx' => 'rexx', + 'netrexx' => 'rexx', + ); + + public function __construct( $html ) { + $this->html = $html; + } + + public function error() { + } + + public function set_language( $language ) { + } + + public function parse_code() { + global $wgOut; + $wgOut->addModuleStyles( 'ext.pygments' ); + return $this->html; + } +} diff --git a/SyntaxHighlight_GeSHi.langs.php b/SyntaxHighlight_GeSHi.langs.php deleted file mode 100644 index 535ec02..0000000 --- a/SyntaxHighlight_GeSHi.langs.php +++ /dev/null @@ -1,245 +0,0 @@ - * @ingroup Maintenance */ +use KzykHys\Pygments\Pygments; + $IP = getenv( 'MW_INSTALL_PATH' ) ?: __DIR__ . '/../../..'; require_once "$IP/maintenance/Maintenance.php"; -require_once __DIR__ . "/../geshi/geshi.php"; -class UpdateLanguageList extends Maintenance { +class UpdateCSS extends Maintenance { + public function __construct() { parent::__construct(); - $this->addDescription( 'Update list of languages supported by SyntaxHighlight_GeSHi' ); + $this->addDescription( 'Generate CSS code for SyntaxHighlight_GeSHi' ); } public function execute() { - global $IP; + global $wgPygmentizePath; - function lang_filter( $val ) { - return preg_match('/^[a-zA-Z0-9\-_]+$/', $val); + $target = __DIR__ . '/../modules/pygments.generated.css'; + $pygments = new Pygments( $wgPygmentizePath ); + $css = "/* Stylesheet generated by updateCSS.php */\n"; + $css .= $pygments->getCss( 'default', '.' . SyntaxHighlight_GeSHi::HIGHLIGHT_CSS_CLASS ); + if ( file_put_contents( $target, $css ) === false ) { + $this->output( "Failed to write to {$target}\n" ); + } else { + $this->output( 'CSS written to ' . realpath( $target ) . "\n" ); } - - $geshi = new GeSHi; - $header = '// Generated by ' . basename( __FILE__ ) . ' on ' . date( DATE_RFC2822 ) . "\n"; - $langs = array_values( array_filter( $geshi->get_supported_languages( false ), 'lang_filter' ) ); - sort( $langs ); - $replace = array( '[' => "array(\n\t", ']' => "\n);\n", '",' => "\",\n\t" ); - $code = "output( "Updated language list written to SyntaxHighlight_GeSHi.langs.php\n" ); } } -$maintClass = "UpdateLanguageList"; -require_once ( RUN_MAINTENANCE_IF_MAIN ); +$maintClass = "UpdateCSS"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/maintenance/updateLanguageList.php b/maintenance/updateLexerList.php similarity index 63% rename from maintenance/updateLanguageList.php rename to maintenance/updateLexerList.php index fe122e9..b8c792c 100644 --- a/maintenance/updateLanguageList.php +++ b/maintenance/updateLexerList.php @@ -1,58 +1,60 @@ * @ingroup Maintenance */ +use KzykHys\Pygments\Pygments; + $IP = getenv( 'MW_INSTALL_PATH' ) ?: __DIR__ . '/../../..'; require_once "$IP/maintenance/Maintenance.php"; -require_once __DIR__ . "/../geshi/geshi.php"; class UpdateLanguageList extends Maintenance { public function __construct() { parent::__construct(); - $this->addDescription( 'Update list of languages supported by SyntaxHighlight_GeSHi' ); + $this->addDescription( 'Update list of lexers supported by SyntaxHighlight_GeSHi' ); } public function execute() { - global $IP; + global $wgPygmentizePath; function lang_filter( $val ) { return preg_match('/^[a-zA-Z0-9\-_]+$/', $val); } - $geshi = new GeSHi; - $header = '// Generated by ' . basename( __FILE__ ) . ' on ' . date( DATE_RFC2822 ) . "\n"; - $langs = array_values( array_filter( $geshi->get_supported_languages( false ), 'lang_filter' ) ); - sort( $langs ); - $replace = array( '[' => "array(\n\t", ']' => "\n);\n", '",' => "\",\n\t" ); - $code = "output( "Updated language list written to SyntaxHighlight_GeSHi.langs.php\n" ); + $header = '// Generated by ' . basename( __FILE__ ) . "\n\n"; + + $pygments = new Pygments( $wgPygmentizePath ); + $lexers = array_keys( $pygments->getLexers() ); + sort( $lexers ); + + $code = "| (?=\())/i', '', $code ); + $code = preg_replace( "/^ +/m", "\t", $code ); + + file_put_contents( __DIR__ . '/../SyntaxHighlight_GeSHi.lexers.php', $code ); + $this->output( "Updated language list written to SyntaxHighlight_GeSHi.lexers.php\n" ); } } $maintClass = "UpdateLanguageList"; require_once ( RUN_MAINTENANCE_IF_MAIN ); diff --git a/modules/pygments.generated.css b/modules/pygments.generated.css new file mode 100644 index 0000000..12b23df --- /dev/null +++ b/modules/pygments.generated.css @@ -0,0 +1,64 @@ +/* Stylesheet generated by updateCSS.php */ +.mw-highlight .hll { background-color: #ffffcc } +.mw-highlight { background: #f8f8f8; } +.mw-highlight .c { color: #408080; font-style: italic } /* Comment */ +.mw-highlight .err { border: 1px solid #FF0000 } /* Error */ +.mw-highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.mw-highlight .o { color: #666666 } /* Operator */ +.mw-highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.mw-highlight .cp { color: #BC7A00 } /* Comment.Preproc */ +.mw-highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.mw-highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ +.mw-highlight .gd { color: #A00000 } /* Generic.Deleted */ +.mw-highlight .ge { font-style: italic } /* Generic.Emph */ +.mw-highlight .gr { color: #FF0000 } /* Generic.Error */ +.mw-highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.mw-highlight .gi { color: #00A000 } /* Generic.Inserted */ +.mw-highlight .go { color: #888888 } /* Generic.Output */ +.mw-highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.mw-highlight .gs { font-weight: bold } /* Generic.Strong */ +.mw-highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.mw-highlight .gt { color: #0044DD } /* Generic.Traceback */ +.mw-highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.mw-highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.mw-highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.mw-highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.mw-highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.mw-highlight .kt { color: #B00040 } /* Keyword.Type */ +.mw-highlight .m { color: #666666 } /* Literal.Number */ +.mw-highlight .s { color: #BA2121 } /* Literal.String */ +.mw-highlight .na { color: #7D9029 } /* Name.Attribute */ +.mw-highlight .nb { color: #008000 } /* Name.Builtin */ +.mw-highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.mw-highlight .no { color: #880000 } /* Name.Constant */ +.mw-highlight .nd { color: #AA22FF } /* Name.Decorator */ +.mw-highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.mw-highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.mw-highlight .nf { color: #0000FF } /* Name.Function */ +.mw-highlight .nl { color: #A0A000 } /* Name.Label */ +.mw-highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.mw-highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.mw-highlight .nv { color: #19177C } /* Name.Variable */ +.mw-highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.mw-highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.mw-highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.mw-highlight .mf { color: #666666 } /* Literal.Number.Float */ +.mw-highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.mw-highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.mw-highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.mw-highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.mw-highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.mw-highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.mw-highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.mw-highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.mw-highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.mw-highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.mw-highlight .sx { color: #008000 } /* Literal.String.Other */ +.mw-highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.mw-highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.mw-highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.mw-highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.mw-highlight .vc { color: #19177C } /* Name.Variable.Class */ +.mw-highlight .vg { color: #19177C } /* Name.Variable.Global */ +.mw-highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.mw-highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/modules/pygments.wrapper.css b/modules/pygments.wrapper.css new file mode 100644 index 0000000..fceb4ab --- /dev/null +++ b/modules/pygments.wrapper.css @@ -0,0 +1,8 @@ +.mw-highlight { + font-family: monospace; +} + +span.mw-highlight { + font-size: 1.2em; + padding: 0.2em; +}