/*! * VisualEditor user interface MWSyntaxHighlightDialog class. * * @copyright 2011-2014 VisualEditor Team and others; see AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * MWSyntaxHighlight highlighter * * @constructor * @param {string} lang The language it is working on */ ve.ce.MWSyntaxHighlightHighlighter = function VeCeMWSyntaxHighlightHighlighter( lang ) { // Highlighter rule cache this.ruleset = {}; this.delimiter = /\n/; // Go to the following page for explanations on language rule files // https://www.mediawiki.org/wiki/Extension:SyntaxHighlight_GeSHi/VisualEditor this.rulePath = ve.init.platform.getModulesUrl() + '/syntaxhighlight/rules/'; this.lang = lang; this.validators = new ve.ce.MWSyntaxHighlightValidator(); this.roster = this.validators.getRoster(); // Properties // 'languageName' : [boolean, string];set to false to disable support, string for language name this.langSupport = { 'text' : [true, 'Plain text'], 'javascript' : [true, 'JavaScript'], 'bf' : [true, 'Brainfuck'], 'ruby' : [true, 'Ruby'], 'python' : [true, 'Python'], 'mysql' : [true, 'MySQL'], 'jquery' : [true, 'jQuery'] }; }; /* Methods */ /** * Initialization * * @method */ ve.ce.MWSyntaxHighlightHighlighter.prototype.initialize = function () { this.loadRules( this.lang ); }; /** * Style each token, based on loaded rules * * @method * @param {Array} tokens Tokens * @param {string} dataString Model data string * @param {boolean} validating Whether to use validator's ruleset instead of highlighter's * @returns {Array} Tokens with stying */ ve.ce.MWSyntaxHighlightHighlighter.prototype.mark = function ( tokens, dataString, validating ) { var regex, match, capture, range, regexGroup, except, within, outside, decision, markRange, pickedRules = validating ? this.ruleset.validator : this.ruleset.highlighter, i, j, k; if ( !validating ){ for ( i = 0; i < pickedRules.length; i++){ regex = this.parseRegex( pickedRules[i].match ); while ( (match = regex.exec( dataString )) !== null ){ // Capturing group; all chars in the group need marking capture = match[1]; // Range of capturing group, [a, b); relative to whole string range = [ match.index + match[0].indexOf(capture), match.index + match[0].indexOf(capture) + capture.length]; // Mark based on indices markRange = this.findToken(tokens, range); for ( j = markRange[0]; j <= markRange[1]; j++ ){ tokens[j].mark.push( pickedRules[i].style ); } } } } else { this.validators.setData( dataString ); for ( i = 0; i < pickedRules.length; i++){ regexGroup = []; except = []; within = []; outside = []; // Initialize ranges for ( j = 0; j < pickedRules[i].except.length; j++ ){ except.push(this.parseRegex( pickedRules[i].except[j] )); } for ( j = 0; j < pickedRules[i].within.length; j++ ){ within.push(this.parseRegex( pickedRules[i].within[j] )); } for ( j = 0; j < pickedRules[i].outside.length; j++ ){ outside.push(this.parseRegex( pickedRules[i].outside[j] )); } this.validators.initRange(except, within, outside); // Parse rule regex for ( j = 0; j < pickedRules[i].match.length; j++ ){ regexGroup.push(this.parseRegex( pickedRules[i].match[j] )); } // Make decision decision = this.roster[pickedRules[i].decisionMaker]( regexGroup, this.validators ); // Mark if (decision.needMarks){ for ( j = 0; j < decision.matches.length; j++ ){ match = decision.matches[j]; capture = match[1]; // Range of capturing group, [a, b); relative to whole string range = [ match.index, match.index + capture.length]; // Mark based on indices markRange = this.findToken(tokens, range); for ( k = markRange[0]; k <= markRange[1]; k++ ){ tokens[k].mark.push( pickedRules[i].style ); tokens[k].tip.push( pickedRules[i].tip ); } } } } } return tokens; }; /** * Convert tokens to DOM tree * * @method * @param {Array} tokens Tokens * @returns {string} HTML of entire tree */ ve.ce.MWSyntaxHighlightHighlighter.prototype.display = function ( tokens ) { var lineContainer = '
'+number+'
';
};
/**
* Build token HTML
*
* @method
* @param {Object} tokenObject Token
* @returns {string}
*/
ve.ce.MWSyntaxHighlightHighlighter.prototype.buildToken = function ( tokenObject ){
var ph = '', tb = '', title = '';
if (tokenObject.ph){ ph = 'ph = " " ';}
if (tokenObject.tb){ tb = 'tb = " " ';}
if (tokenObject.title !== ''){ title = 'title = "'+tokenObject.title+'" ';}
return (
''+
tokenObject.text+
''
);
};
/**
* Build line HTML
*
* @method
* @param {Object} lineObject Line
* @returns {string}
*/
ve.ce.MWSyntaxHighlightHighlighter.prototype.buildLine = function ( lineObject ){
return ''+lineObject.text+''; }; /** * Parse pre-defined JSON file * * @method * @param {string} lang The language (as filename) */ ve.ce.MWSyntaxHighlightHighlighter.prototype.loadRules = function ( lang ) { var dataClosure = null; jQuery.ajax({ 'async': false, 'global': false, 'url': this.rulePath + lang + '.json', 'dataType': 'json', 'success': function (data) { dataClosure = data;} }); this.ruleset = dataClosure; }; /** * Parse string to RegExp object * * @method * @param {string} regexString * @returns {Object} RegExp object */ ve.ce.MWSyntaxHighlightHighlighter.prototype.parseRegex = function ( regexString ) { var regex = regexString.match(/^\/(.*)\/[igm]*$/)[1], modifier = regexString.match(/^\/.*\/([igm]*$)/)[1]; return new RegExp( regex, modifier ); }; /** * Get the list of supported languages * * @method * @returns {Object} Object describing all supported languages */ ve.ce.MWSyntaxHighlightHighlighter.prototype.getSupportedLanguages = function () { return this.langSupport; }; /** * Get the name of this language * * @method * @param {String} (Optional) Language to be referenced * @returns {String} Language name */ ve.ce.MWSyntaxHighlightHighlighter.prototype.getLanguageName = function ( lang ) { if (lang === undefined){ return this.langSupport[ this.lang ][1]; } else { return this.langSupport[ lang ][1]; } }; /** * Check whether the language is supported * * @method * @returns {boolean} */ ve.ce.MWSyntaxHighlightHighlighter.prototype.isSupportedLanguage = function () { return this.langSupport.hasOwnProperty( this.lang ); }; /** * Check whether the support for the language is enabled * * @method * @returns {boolean} */ ve.ce.MWSyntaxHighlightHighlighter.prototype.isEnabledLanguage = function () { return this.langSupport[ this.lang ][0]; }; /** * Find all tokens that overlap with the given range * * @method * @param {Array} tokens Tokens * @param {Array} range Array describing model range * @returns {Array} Range of token indices */ ve.ce.MWSyntaxHighlightHighlighter.prototype.findToken = function ( tokens, range ) { var items = [], i; for ( i = 0; i < tokens.length; i++ ){ if ( ( tokens[i].index <= range[0] && tokens[i].index + tokens[i].text.length > range[0] ) || ( tokens[i].index > range[0] && tokens[i].index + tokens[i].text.length < range[1] ) || ( tokens[i].index < range[1] && tokens[i].index + tokens[i].text.length >= range[1] ) ){ items.push(tokens[i]); } } if (items.length === 1){ i = tokens.indexOf( items[0] ); return [i, i]; } else { return [tokens.indexOf( items[0] ), tokens.indexOf( items[items.length - 1] )]; } };