// CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: https://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 startNewLine(stream, state) { state.mode = Modes.newLayout state.tableHeading = false if (state.layoutType === 'definitionList' && state.spanningLayout && stream.match(RE('definitionListEnd'), false)) state.spanningLayout = false } function handlePhraseModifier(stream, state, ch) { if (ch === '_') { if (stream.eat('_')) return togglePhraseModifier(stream, state, 'italic', /__/, 2) else return togglePhraseModifier(stream, state, 'em', /_/, 1) } if (ch === '*') { if (stream.eat('*')) { return togglePhraseModifier(stream, state, 'bold', /\*\*/, 2) } return togglePhraseModifier(stream, state, 'strong', /\*/, 1) } if (ch === '[') { if (stream.match(/\d+\]/)) state.footCite = true return tokenStyles(state) } if (ch === '(') { var spec = stream.match(/^(r|tm|c)\)/) if (spec) return tokenStylesWith(state, TOKEN_STYLES.specialChar) } if (ch === '<' && stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) return tokenStylesWith(state, TOKEN_STYLES.html) if (ch === '?' && stream.eat('?')) return togglePhraseModifier(stream, state, 'cite', /\?\?/, 2) if (ch === '=' && stream.eat('=')) return togglePhraseModifier(stream, state, 'notextile', /==/, 2) if (ch === '-' && !stream.eat('-')) return togglePhraseModifier(stream, state, 'deletion', /-/, 1) if (ch === '+') return togglePhraseModifier(stream, state, 'addition', /\+/, 1) if (ch === '~') return togglePhraseModifier(stream, state, 'sub', /~/, 1) if (ch === '^') return togglePhraseModifier(stream, state, 'sup', /\^/, 1) if (ch === '%') return togglePhraseModifier(stream, state, 'span', /%/, 1) if (ch === '@') return togglePhraseModifier(stream, state, 'code', /@/, 1) if (ch === '!') { var type = togglePhraseModifier(stream, state, 'image', /(?:\([^\)]+\))?!/, 1) stream.match(/^:\S+/) // optional Url portion return type } return tokenStyles(state) } function togglePhraseModifier(stream, state, phraseModifier, closeRE, openSize) { var charBefore = stream.pos > openSize ? stream.string.charAt(stream.pos - openSize - 1) : null var charAfter = stream.peek() if (state[phraseModifier]) { if ((!charAfter || /\W/.test(charAfter)) && charBefore && /\S/.test(charBefore)) { var type = tokenStyles(state) state[phraseModifier] = false return type } } else if ((!charBefore || /\W/.test(charBefore)) && charAfter && /\S/.test(charAfter) && stream.match(new RegExp('^.*\\S' + closeRE.source + '(?:\\W|$)'), false)) { state[phraseModifier] = true state.mode = Modes.attributes } return tokenStyles(state) } function tokenStyles(state) { var disabled = textileDisabled(state) if (disabled) return disabled var styles = [] if (state.layoutType) styles.push(TOKEN_STYLES[state.layoutType]) styles = styles.concat( activeStyles(state, 'addition', 'bold', 'cite', 'code', 'deletion', 'em', 'footCite', 'image', 'italic', 'link', 'span', 'strong', 'sub', 'sup', 'table', 'tableHeading') ) if (state.layoutType === 'header') styles.push(TOKEN_STYLES.header + '-' + state.header) return styles.length ? styles.join(' ') : null } function textileDisabled(state) { var type = state.layoutType switch (type) { case 'notextile': case 'code': case 'pre': return TOKEN_STYLES[type] default: if (state.notextile) return TOKEN_STYLES.notextile + (type ? ' ' + TOKEN_STYLES[type] : '') return null } } function tokenStylesWith(state, extraStyles) { var disabled = textileDisabled(state) if (disabled) return disabled var type = tokenStyles(state) if (extraStyles) return type ? type + ' ' + extraStyles : extraStyles else return type } function activeStyles(state) { var styles = [] for (var i = 1; i < arguments.length; ++i) { if (state[arguments[i]]) styles.push(TOKEN_STYLES[arguments[i]]) } return styles } function blankLine(state) { var spanningLayout = state.spanningLayout, type = state.layoutType for (var key in state) if (state.hasOwnProperty(key)) delete state[key] state.mode = Modes.newLayout if (spanningLayout) { state.layoutType = type state.spanningLayout = true } } var REs = { cache: {}, 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: /[^!"_=\*\[\(<\?\+~\^%@-]+/, }, attributes: { align: /(?:<>|<|>|=)/, selector: /\([^\(][^\)]+\)/, lang: /\[[^\[\]]+\]/, pad: /(?:\(+|\)+){1,2}/, css: /\{[^\}]+\}/, }, createRe: function (name) { switch (name) { case 'drawTable': return REs.makeRe('^', REs.single.drawTable, '$') case 'html': return REs.makeRe('^', REs.single.html, '(?:', REs.single.html, ')*', '$') case 'linkDefinition': return REs.makeRe('^', REs.single.linkDefinition, '$') case 'listLayout': return REs.makeRe('^', REs.single.list, RE('allAttributes'), '*\\s+') case 'tableCellAttributes': return REs.makeRe('^', REs.choiceRe(REs.single.tableCellAttributes, RE('allAttributes')), '+\\.') case 'type': return REs.makeRe('^', RE('allTypes')) case 'typeLayout': return REs.makeRe('^', RE('allTypes'), RE('allAttributes'), '*\\.\\.?', '(\\s+|$)') case 'attributes': return REs.makeRe('^', RE('allAttributes'), '+') case 'allTypes': return REs.choiceRe(REs.single.div, REs.single.foot, REs.single.header, REs.single.bc, REs.single.bq, REs.single.notextile, REs.single.pre, REs.single.table, REs.single.para) case 'allAttributes': return REs.choiceRe(REs.attributes.selector, REs.attributes.css, REs.attributes.lang, REs.attributes.align, REs.attributes.pad) default: return REs.makeRe('^', REs.single[name]) } }, makeRe: function () { var pattern = '' for (var i = 0; i < arguments.length; ++i) { var arg = arguments[i] pattern += typeof arg === 'string' ? arg : arg.source } return new RegExp(pattern) }, choiceRe: function () { var parts = [arguments[0]] for (var i = 1; i < arguments.length; ++i) { parts[i * 2 - 1] = '|' parts[i * 2] = arguments[i] } parts.unshift('(?:') parts.push(')') return REs.makeRe.apply(null, parts) }, } function RE(name) { return REs.cache[name] || (REs.cache[name] = REs.createRe(name)) } var Modes = { newLayout: function (stream, state) { if (stream.match(RE('typeLayout'), false)) { state.spanningLayout = false return (state.mode = Modes.blockType)(stream, state) } var newMode if (!textileDisabled(state)) { if (stream.match(RE('listLayout'), false)) newMode = Modes.list else if (stream.match(RE('drawTable'), false)) newMode = Modes.table else if (stream.match(RE('linkDefinition'), false)) newMode = Modes.linkDefinition else if (stream.match(RE('definitionList'))) newMode = Modes.definitionList else if (stream.match(RE('html'), false)) newMode = Modes.html } return (state.mode = newMode || Modes.text)(stream, state) }, blockType: function (stream, state) { var match, type state.layoutType = null if ((match = stream.match(RE('type')))) type = match[0] else return (state.mode = Modes.text)(stream, state) if ((match = type.match(RE('header')))) { state.layoutType = 'header' state.header = parseInt(match[0][1]) } else if (type.match(RE('bq'))) { state.layoutType = 'quote' } else if (type.match(RE('bc'))) { state.layoutType = 'code' } else if (type.match(RE('foot'))) { state.layoutType = 'footnote' } else if (type.match(RE('notextile'))) { state.layoutType = 'notextile' } else if (type.match(RE('pre'))) { state.layoutType = 'pre' } else if (type.match(RE('div'))) { state.layoutType = 'div' } else if (type.match(RE('table'))) { state.layoutType = 'table' } state.mode = Modes.attributes return tokenStyles(state) }, text: function (stream, state) { if (stream.match(RE('text'))) return tokenStyles(state) var ch = stream.next() if (ch === '"') return (state.mode = Modes.link)(stream, state) return handlePhraseModifier(stream, state, ch) }, attributes: function (stream, state) { state.mode = Modes.layoutLength if (stream.match(RE('attributes'))) return tokenStylesWith(state, TOKEN_STYLES.attributes) else return tokenStyles(state) }, layoutLength: function (stream, state) { if (stream.eat('.') && stream.eat('.')) state.spanningLayout = true state.mode = Modes.text return tokenStyles(state) }, list: function (stream, state) { var match = stream.match(RE('list')) state.listDepth = match[0].length var listMod = (state.listDepth - 1) % 3 if (!listMod) state.layoutType = 'list1' else if (listMod === 1) state.layoutType = 'list2' else state.layoutType = 'list3' state.mode = Modes.attributes return tokenStyles(state) }, link: function (stream, state) { state.mode = Modes.text if (stream.match(RE('link'))) { stream.match(/\S+/) return tokenStylesWith(state, TOKEN_STYLES.link) } return tokenStyles(state) }, linkDefinition: function (stream, state) { stream.skipToEnd() return tokenStylesWith(state, TOKEN_STYLES.linkDefinition) }, definitionList: function (stream, state) { stream.match(RE('definitionList')) state.layoutType = 'definitionList' if (stream.match(/\s*$/)) state.spanningLayout = true else state.mode = Modes.attributes return tokenStyles(state) }, html: function (stream, state) { stream.skipToEnd() return tokenStylesWith(state, TOKEN_STYLES.html) }, table: function (stream, state) { state.layoutType = 'table' return (state.mode = Modes.tableCell)(stream, state) }, tableCell: function (stream, state) { if (stream.match(RE('tableHeading'))) state.tableHeading = true else stream.eat('|') state.mode = Modes.tableCellAttributes return tokenStyles(state) }, tableCellAttributes: function (stream, state) { state.mode = Modes.tableText if (stream.match(RE('tableCellAttributes'))) return tokenStylesWith(state, TOKEN_STYLES.attributes) else return tokenStyles(state) }, tableText: function (stream, state) { if (stream.match(RE('tableText'))) return tokenStyles(state) if (stream.peek() === '|') { // end of cell state.mode = Modes.tableCell return tokenStyles(state) } return handlePhraseModifier(stream, state, stream.next()) }, } CodeMirror.defineMode('textile', function () { return { startState: function () { return { mode: Modes.newLayout } }, token: function (stream, state) { if (stream.sol()) startNewLine(stream, state) return state.mode(stream, state) }, blankLine: blankLine, } }) CodeMirror.defineMIME('text/x-textile', 'textile') })