// 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'); });