mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/CodeMirror
synced 2024-12-20 01:30:33 +00:00
554 lines
14 KiB
JavaScript
554 lines
14 KiB
JavaScript
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||
|
|
||
|
(function(mod) {
|
||
|
if (typeof exports == 'object' && typeof module == 'object') { // CommonJS
|
||
|
mod(require('../../lib/codemirror'));
|
||
|
} else if (typeof define == 'function' && define.amd) { // AMD
|
||
|
define(['../../lib/codemirror'], mod);
|
||
|
} else { // Plain browser env
|
||
|
mod(CodeMirror);
|
||
|
}
|
||
|
})(function(CodeMirror) {
|
||
|
'use strict';
|
||
|
|
||
|
var TOKEN_STYLES = {
|
||
|
addition: 'positive',
|
||
|
attributes: 'attribute',
|
||
|
bold: 'strong',
|
||
|
cite: 'keyword',
|
||
|
code: 'atom',
|
||
|
definitionList: 'number',
|
||
|
deletion: 'negative',
|
||
|
div: 'punctuation',
|
||
|
em: 'em',
|
||
|
footnote: 'variable',
|
||
|
footCite: 'qualifier',
|
||
|
header: 'header',
|
||
|
html: 'comment',
|
||
|
image: 'string',
|
||
|
italic: 'em',
|
||
|
link: 'link',
|
||
|
linkDefinition: 'link',
|
||
|
list1: 'variable-2',
|
||
|
list2: 'variable-3',
|
||
|
list3: 'keyword',
|
||
|
notextile: 'string-2',
|
||
|
pre: 'operator',
|
||
|
p: 'property',
|
||
|
quote: 'bracket',
|
||
|
span: 'quote',
|
||
|
specialChar: 'tag',
|
||
|
strong: 'strong',
|
||
|
sub: 'builtin',
|
||
|
sup: 'builtin',
|
||
|
table: 'variable-3',
|
||
|
tableHeading: 'operator'
|
||
|
};
|
||
|
|
||
|
function Parser(regExpFactory, state, stream) {
|
||
|
this.regExpFactory = regExpFactory;
|
||
|
this.state = state;
|
||
|
this.stream = stream;
|
||
|
this.styles = TOKEN_STYLES;
|
||
|
|
||
|
this.state.specialChar = null;
|
||
|
}
|
||
|
|
||
|
Parser.prototype.eat = function(name) {
|
||
|
return this.stream.match(this.regExpFactory.pattern(name), true);
|
||
|
};
|
||
|
|
||
|
Parser.prototype.check = function(name) {
|
||
|
return this.stream.match(this.regExpFactory.pattern(name), false);
|
||
|
};
|
||
|
|
||
|
Parser.prototype.setModeForNextToken = function(mode) {
|
||
|
return this.state.mode = mode;
|
||
|
};
|
||
|
|
||
|
Parser.prototype.execMode = function(newMode) {
|
||
|
return this.setModeForNextToken(newMode).call(this);
|
||
|
};
|
||
|
|
||
|
Parser.prototype.startNewLine = function() {
|
||
|
this.setModeForNextToken(Modes.newLayout);
|
||
|
this.state.tableHeading = false;
|
||
|
|
||
|
if (this.state.layoutType === 'definitionList' && this.state.spanningLayout) {
|
||
|
if (this.check('definitionListEnd')) {
|
||
|
this.state.spanningLayout = false;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Parser.prototype.nextToken = function() {
|
||
|
return this.state.mode.call(this);
|
||
|
};
|
||
|
|
||
|
Parser.prototype.styleFor = function(token) {
|
||
|
if (this.styles.hasOwnProperty(token)) {
|
||
|
return this.styles[token];
|
||
|
}
|
||
|
throw 'unknown token';
|
||
|
};
|
||
|
|
||
|
Parser.prototype.handlePhraseModifier = function(ch) {
|
||
|
if (ch === '_') {
|
||
|
if (this.stream.eat('_')) {
|
||
|
return this.togglePhraseModifier('italic', /^.*__/);
|
||
|
}
|
||
|
return this.togglePhraseModifier('em', /^.*_/);
|
||
|
}
|
||
|
|
||
|
if (ch === '*') {
|
||
|
if (this.stream.eat('*')) {
|
||
|
return this.togglePhraseModifier('bold', /^.*\*\*/);
|
||
|
}
|
||
|
return this.togglePhraseModifier('strong', /^.*\*/);
|
||
|
}
|
||
|
|
||
|
if (ch === '[') {
|
||
|
if (this.stream.match(/\d+\]/)) {
|
||
|
this.state.footCite = true;
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
}
|
||
|
|
||
|
if (ch === '(') {
|
||
|
if (this.stream.match('r)')) {
|
||
|
this.state.specialChar = 'r';
|
||
|
} else if (this.stream.match('tm)')) {
|
||
|
this.state.specialChar = 'tm';
|
||
|
} else if (this.stream.match('c)')) {
|
||
|
this.state.specialChar = 'c';
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
}
|
||
|
|
||
|
if (ch === '<') {
|
||
|
if (this.stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) {
|
||
|
return this.tokenStylesWith(this.styleFor('html'));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ch === '?' && this.stream.eat('?')) {
|
||
|
return this.togglePhraseModifier('cite', /^.*\?\?/);
|
||
|
}
|
||
|
if (ch === '=' && this.stream.eat('=')) {
|
||
|
return this.togglePhraseModifier('notextile', /^.*==/);
|
||
|
}
|
||
|
if (ch === '-') {
|
||
|
return this.togglePhraseModifier('deletion', /^.*-/);
|
||
|
}
|
||
|
if (ch === '+') {
|
||
|
return this.togglePhraseModifier('addition', /^.*\+/);
|
||
|
}
|
||
|
if (ch === '~') {
|
||
|
return this.togglePhraseModifier('sub', /^.*~/);
|
||
|
}
|
||
|
if (ch === '^') {
|
||
|
return this.togglePhraseModifier('sup', /^.*\^/);
|
||
|
}
|
||
|
if (ch === '%') {
|
||
|
return this.togglePhraseModifier('span', /^.*%/);
|
||
|
}
|
||
|
if (ch === '@') {
|
||
|
return this.togglePhraseModifier('code', /^.*@/);
|
||
|
}
|
||
|
if (ch === '!') {
|
||
|
var type = this.togglePhraseModifier('image', /^.*(?:\([^\)]+\))?!/);
|
||
|
this.stream.match(/^:\S+/); // optional Url portion
|
||
|
return type;
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
};
|
||
|
|
||
|
Parser.prototype.togglePhraseModifier = function(phraseModifier, closeRE) {
|
||
|
if (this.state[phraseModifier]) { // remove phrase modifier
|
||
|
var type = this.tokenStyles();
|
||
|
this.state[phraseModifier] = false;
|
||
|
return type;
|
||
|
}
|
||
|
if (this.stream.match(closeRE, false)) { // add phrase modifier
|
||
|
this.state[phraseModifier] = true;
|
||
|
this.setModeForNextToken(Modes.attributes);
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
};
|
||
|
|
||
|
Parser.prototype.tokenStyles = function() {
|
||
|
var disabled = this.textileDisabled(),
|
||
|
styles = [];
|
||
|
|
||
|
if (disabled) return disabled;
|
||
|
|
||
|
if (this.state.layoutType) {
|
||
|
styles.push(this.styleFor(this.state.layoutType));
|
||
|
}
|
||
|
|
||
|
styles = styles.concat(this.activeStyles('addition', 'bold', 'cite', 'code',
|
||
|
'deletion', 'em', 'footCite', 'image', 'italic', 'link', 'span', 'specialChar', 'strong',
|
||
|
'sub', 'sup', 'table', 'tableHeading'));
|
||
|
|
||
|
if (this.state.layoutType === 'header') {
|
||
|
styles.push(this.styleFor('header') + '-' + this.state.header);
|
||
|
}
|
||
|
return styles.length ? styles.join(' ') : null;
|
||
|
};
|
||
|
|
||
|
Parser.prototype.textileDisabled = function() {
|
||
|
var type = this.state.layoutType;
|
||
|
|
||
|
switch(type) {
|
||
|
case 'notextile':
|
||
|
case 'code':
|
||
|
case 'pre':
|
||
|
return this.styleFor(type);
|
||
|
default:
|
||
|
if (this.state.notextile) {
|
||
|
return this.styleFor('notextile') + (type ? (' ' + this.styleFor(type)) : '');
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Parser.prototype.tokenStylesWith = function(extraStyles) {
|
||
|
var disabled = this.textileDisabled(),
|
||
|
type;
|
||
|
|
||
|
if (disabled) return disabled;
|
||
|
|
||
|
type = this.tokenStyles();
|
||
|
if(extraStyles) {
|
||
|
return type ? (type + ' ' + extraStyles) : extraStyles;
|
||
|
}
|
||
|
return type;
|
||
|
};
|
||
|
|
||
|
Parser.prototype.activeStyles = function() {
|
||
|
var styles = [],
|
||
|
i;
|
||
|
for (i = 0; i < arguments.length; ++i) {
|
||
|
if (this.state[arguments[i]]) {
|
||
|
styles.push(this.styleFor(arguments[i]));
|
||
|
}
|
||
|
}
|
||
|
return styles;
|
||
|
};
|
||
|
|
||
|
Parser.prototype.blankLine = function() {
|
||
|
var spanningLayout = this.state.spanningLayout,
|
||
|
type = this.state.layoutType,
|
||
|
key;
|
||
|
|
||
|
for (key in this.state) {
|
||
|
if (this.state.hasOwnProperty(key)) {
|
||
|
delete this.state[key];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.setModeForNextToken(Modes.newLayout);
|
||
|
if (spanningLayout) {
|
||
|
this.state.layoutType = type;
|
||
|
this.state.spanningLayout = true;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
function RegExpFactory() {
|
||
|
this.cache = {};
|
||
|
this.single = {
|
||
|
bc: 'bc',
|
||
|
bq: 'bq',
|
||
|
definitionList: /- [^(?::=)]+:=+/,
|
||
|
definitionListEnd: /.*=:\s*$/,
|
||
|
div: 'div',
|
||
|
drawTable: /\|.*\|/,
|
||
|
foot: /fn\d+/,
|
||
|
header: /h[1-6]/,
|
||
|
html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/,
|
||
|
link: /[^"]+":\S/,
|
||
|
linkDefinition: /\[[^\s\]]+\]\S+/,
|
||
|
list: /(?:#+|\*+)/,
|
||
|
notextile: 'notextile',
|
||
|
para: 'p',
|
||
|
pre: 'pre',
|
||
|
table: 'table',
|
||
|
tableCellAttributes: /[/\\]\d+/,
|
||
|
tableHeading: /\|_\./,
|
||
|
tableText: /[^"_\*\[\(\?\+~\^%@|-]+/,
|
||
|
text: /[^!"_=\*\[\(<\?\+~\^%@-]+/
|
||
|
};
|
||
|
this.attributes = {
|
||
|
align: /(?:<>|<|>|=)/,
|
||
|
selector: /\([^\(][^\)]+\)/,
|
||
|
lang: /\[[^\[\]]+\]/,
|
||
|
pad: /(?:\(+|\)+){1,2}/,
|
||
|
css: /\{[^\}]+\}/
|
||
|
};
|
||
|
}
|
||
|
|
||
|
RegExpFactory.prototype.pattern = function(name) {
|
||
|
return (this.cache[name] || this.createRe(name));
|
||
|
};
|
||
|
|
||
|
RegExpFactory.prototype.createRe = function(name) {
|
||
|
switch (name) {
|
||
|
case 'drawTable':
|
||
|
return this.makeRe('^', this.single.drawTable, '$');
|
||
|
case 'html':
|
||
|
return this.makeRe('^', this.single.html, '(?:', this.single.html, ')*', '$');
|
||
|
case 'linkDefinition':
|
||
|
return this.makeRe('^', this.single.linkDefinition, '$');
|
||
|
case 'listLayout':
|
||
|
return this.makeRe('^', this.single.list, this.pattern('allAttributes'), '*\\s+');
|
||
|
case 'tableCellAttributes':
|
||
|
return this.makeRe('^', this.choiceRe(this.single.tableCellAttributes,
|
||
|
this.pattern('allAttributes')), '+\\.');
|
||
|
case 'type':
|
||
|
return this.makeRe('^', this.pattern('allTypes'));
|
||
|
case 'typeLayout':
|
||
|
return this.makeRe('^', this.pattern('allTypes'), this.pattern('allAttributes'),
|
||
|
'*\\.\\.?', '(\\s+|$)');
|
||
|
case 'attributes':
|
||
|
return this.makeRe('^', this.pattern('allAttributes'), '+');
|
||
|
|
||
|
case 'allTypes':
|
||
|
return this.choiceRe(this.single.div, this.single.foot,
|
||
|
this.single.header, this.single.bc, this.single.bq,
|
||
|
this.single.notextile, this.single.pre, this.single.table,
|
||
|
this.single.para);
|
||
|
|
||
|
case 'allAttributes':
|
||
|
return this.choiceRe(this.attributes.selector, this.attributes.css,
|
||
|
this.attributes.lang, this.attributes.align, this.attributes.pad);
|
||
|
|
||
|
default:
|
||
|
return this.makeRe('^', this.single[name]);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
RegExpFactory.prototype.makeRe = function() {
|
||
|
var pattern = '',
|
||
|
i,
|
||
|
arg;
|
||
|
|
||
|
for (i = 0; i < arguments.length; ++i) {
|
||
|
arg = arguments[i];
|
||
|
pattern += (typeof arg === 'string') ? arg : arg.source;
|
||
|
}
|
||
|
return new RegExp(pattern);
|
||
|
};
|
||
|
|
||
|
RegExpFactory.prototype.choiceRe = function() {
|
||
|
var parts = [arguments[0]],
|
||
|
i;
|
||
|
|
||
|
for (i = 1; i < arguments.length; ++i) {
|
||
|
parts[i * 2 - 1] = '|';
|
||
|
parts[i * 2] = arguments[i];
|
||
|
}
|
||
|
|
||
|
parts.unshift('(?:');
|
||
|
parts.push(')');
|
||
|
return this.makeRe.apply(this, parts);
|
||
|
};
|
||
|
|
||
|
|
||
|
var Modes = {
|
||
|
newLayout: function() {
|
||
|
if (this.check('typeLayout')) {
|
||
|
this.state.spanningLayout = false;
|
||
|
return this.execMode(Modes.blockType);
|
||
|
}
|
||
|
if (!this.textileDisabled()) {
|
||
|
if (this.check('listLayout')) {
|
||
|
return this.execMode(Modes.list);
|
||
|
} else if (this.check('drawTable')) {
|
||
|
return this.execMode(Modes.table);
|
||
|
} else if (this.check('linkDefinition')) {
|
||
|
return this.execMode(Modes.linkDefinition);
|
||
|
} else if (this.check('definitionList')) {
|
||
|
return this.execMode(Modes.definitionList);
|
||
|
} else if (this.check('html')) {
|
||
|
return this.execMode(Modes.html);
|
||
|
}
|
||
|
}
|
||
|
return this.execMode(Modes.text);
|
||
|
},
|
||
|
|
||
|
blockType: function() {
|
||
|
var match,
|
||
|
type;
|
||
|
this.state.layoutType = null;
|
||
|
|
||
|
if (match = this.eat('type')) {
|
||
|
type = match[0];
|
||
|
} else {
|
||
|
return this.execMode(Modes.text);
|
||
|
}
|
||
|
|
||
|
if(match = type.match(this.regExpFactory.pattern('header'))) {
|
||
|
this.state.layoutType = 'header';
|
||
|
this.state.header = parseInt(match[0][1]);
|
||
|
} else if (type.match(this.regExpFactory.pattern('bq'))) {
|
||
|
this.state.layoutType = 'quote';
|
||
|
} else if (type.match(this.regExpFactory.pattern('bc'))) {
|
||
|
this.state.layoutType = 'code';
|
||
|
} else if (type.match(this.regExpFactory.pattern('foot'))) {
|
||
|
this.state.layoutType = 'footnote';
|
||
|
} else if (type.match(this.regExpFactory.pattern('notextile'))) {
|
||
|
this.state.layoutType = 'notextile';
|
||
|
} else if (type.match(this.regExpFactory.pattern('pre'))) {
|
||
|
this.state.layoutType = 'pre';
|
||
|
} else if (type.match(this.regExpFactory.pattern('div'))) {
|
||
|
this.state.layoutType = 'div';
|
||
|
} else if (type.match(this.regExpFactory.pattern('table'))) {
|
||
|
this.state.layoutType = 'table';
|
||
|
}
|
||
|
|
||
|
this.setModeForNextToken(Modes.attributes);
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
text: function() {
|
||
|
if (this.eat('text')) {
|
||
|
return this.tokenStyles();
|
||
|
}
|
||
|
|
||
|
var ch = this.stream.next();
|
||
|
|
||
|
if (ch === '"') {
|
||
|
return this.execMode(Modes.link);
|
||
|
}
|
||
|
return this.handlePhraseModifier(ch);
|
||
|
},
|
||
|
|
||
|
attributes: function() {
|
||
|
this.setModeForNextToken(Modes.layoutLength);
|
||
|
|
||
|
if (this.eat('attributes')) {
|
||
|
return this.tokenStylesWith(this.styleFor('attributes'));
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
layoutLength: function() {
|
||
|
if (this.stream.eat('.') && this.stream.eat('.')) {
|
||
|
this.state.spanningLayout = true;
|
||
|
}
|
||
|
|
||
|
this.setModeForNextToken(Modes.text);
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
list: function() {
|
||
|
var match = this.eat('list'),
|
||
|
listMod;
|
||
|
this.state.listDepth = match[0].length;
|
||
|
listMod = (this.state.listDepth - 1) % 3;
|
||
|
if (!listMod) {
|
||
|
this.state.layoutType = 'list1';
|
||
|
} else if (listMod === 1) {
|
||
|
this.state.layoutType = 'list2';
|
||
|
} else {
|
||
|
this.state.layoutType = 'list3';
|
||
|
}
|
||
|
this.setModeForNextToken(Modes.attributes);
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
link: function() {
|
||
|
this.setModeForNextToken(Modes.text);
|
||
|
if (this.eat('link')) {
|
||
|
this.stream.match(/\S+/);
|
||
|
return this.tokenStylesWith(this.styleFor('link'));
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
linkDefinition: function() {
|
||
|
this.stream.skipToEnd();
|
||
|
return this.tokenStylesWith(this.styleFor('linkDefinition'));
|
||
|
},
|
||
|
|
||
|
definitionList: function() {
|
||
|
this.eat('definitionList');
|
||
|
|
||
|
this.state.layoutType = 'definitionList';
|
||
|
|
||
|
if (this.stream.match(/\s*$/)) {
|
||
|
this.state.spanningLayout = true;
|
||
|
} else {
|
||
|
this.setModeForNextToken(Modes.attributes);
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
html: function() {
|
||
|
this.stream.skipToEnd();
|
||
|
return this.tokenStylesWith(this.styleFor('html'));
|
||
|
},
|
||
|
|
||
|
table: function() {
|
||
|
this.state.layoutType = 'table';
|
||
|
return this.execMode(Modes.tableCell);
|
||
|
},
|
||
|
|
||
|
tableCell: function() {
|
||
|
if (this.eat('tableHeading')) {
|
||
|
this.state.tableHeading = true;
|
||
|
} else {
|
||
|
this.stream.eat('|');
|
||
|
}
|
||
|
this.setModeForNextToken(Modes.tableCellAttributes);
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
tableCellAttributes: function() {
|
||
|
this.setModeForNextToken(Modes.tableText);
|
||
|
|
||
|
if (this.eat('tableCellAttributes')) {
|
||
|
return this.tokenStylesWith(this.styleFor('attributes'));
|
||
|
}
|
||
|
return this.tokenStyles();
|
||
|
},
|
||
|
|
||
|
tableText: function() {
|
||
|
if (this.eat('tableText')) {
|
||
|
return this.tokenStyles();
|
||
|
}
|
||
|
|
||
|
if (this.stream.peek() === '|') { // end of cell
|
||
|
this.setModeForNextToken(Modes.tableCell);
|
||
|
return this.tokenStyles();
|
||
|
}
|
||
|
return this.handlePhraseModifier(this.stream.next());
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
CodeMirror.defineMode('textile', function() {
|
||
|
var regExpFactory = new RegExpFactory();
|
||
|
|
||
|
return {
|
||
|
startState: function() {
|
||
|
return { mode: Modes.newLayout };
|
||
|
},
|
||
|
token: function(stream, state) {
|
||
|
var parser = new Parser(regExpFactory, state, stream);
|
||
|
if (stream.sol()) { parser.startNewLine(); }
|
||
|
return parser.nextToken();
|
||
|
},
|
||
|
blankLine: function(state) {
|
||
|
new Parser(regExpFactory, state).blankLine();
|
||
|
}
|
||
|
};
|
||
|
});
|
||
|
|
||
|
CodeMirror.defineMIME('text/x-textile', 'textile');
|
||
|
});
|