123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- // CodeMirror, copyright (c) by Marijn Haverbeke and others
- // Distributed under an MIT license: https://codemirror.net/LICENSE
- // Slim Highlighting for CodeMirror copyright (c) HicknHack Software Gmbh
- ;(function (mod) {
- if (typeof exports == 'object' && typeof module == 'object')
- // CommonJS
- mod(require('../../lib/codemirror'), require('../htmlmixed/htmlmixed'), require('../ruby/ruby'))
- else if (typeof define == 'function' && define.amd)
- // AMD
- define(['../../lib/codemirror', '../htmlmixed/htmlmixed', '../ruby/ruby'], mod)
- // Plain browser env
- else mod(CodeMirror)
- })(function (CodeMirror) {
- 'use strict'
- CodeMirror.defineMode(
- 'slim',
- function (config) {
- var htmlMode = CodeMirror.getMode(config, { name: 'htmlmixed' })
- var rubyMode = CodeMirror.getMode(config, 'ruby')
- var modes = { html: htmlMode, ruby: rubyMode }
- var embedded = {
- ruby: 'ruby',
- javascript: 'javascript',
- css: 'text/css',
- sass: 'text/x-sass',
- scss: 'text/x-scss',
- less: 'text/x-less',
- styl: 'text/x-styl', // no highlighting so far
- coffee: 'coffeescript',
- asciidoc: 'text/x-asciidoc',
- markdown: 'text/x-markdown',
- textile: 'text/x-textile', // no highlighting so far
- creole: 'text/x-creole', // no highlighting so far
- wiki: 'text/x-wiki', // no highlighting so far
- mediawiki: 'text/x-mediawiki', // no highlighting so far
- rdoc: 'text/x-rdoc', // no highlighting so far
- builder: 'text/x-builder', // no highlighting so far
- nokogiri: 'text/x-nokogiri', // no highlighting so far
- erb: 'application/x-erb',
- }
- var embeddedRegexp = (function (map) {
- var arr = []
- for (var key in map) arr.push(key)
- return new RegExp('^(' + arr.join('|') + '):')
- })(embedded)
- var styleMap = {
- commentLine: 'comment',
- slimSwitch: 'operator special',
- slimTag: 'tag',
- slimId: 'attribute def',
- slimClass: 'attribute qualifier',
- slimAttribute: 'attribute',
- slimSubmode: 'keyword special',
- closeAttributeTag: null,
- slimDoctype: null,
- lineContinuation: null,
- }
- var closing = {
- '{': '}',
- '[': ']',
- '(': ')',
- }
- var nameStartChar = '_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD'
- var nameChar = nameStartChar + '\\-0-9\xB7\u0300-\u036F\u203F-\u2040'
- var nameRegexp = new RegExp('^[:' + nameStartChar + '](?::[' + nameChar + ']|[' + nameChar + ']*)')
- var attributeNameRegexp = new RegExp('^[:' + nameStartChar + '][:\\.' + nameChar + ']*(?=\\s*=)')
- var wrappedAttributeNameRegexp = new RegExp('^[:' + nameStartChar + '][:\\.' + nameChar + ']*')
- var classNameRegexp = /^\.-?[_a-zA-Z]+[\w\-]*/
- var classIdRegexp = /^#[_a-zA-Z]+[\w\-]*/
- function backup(pos, tokenize, style) {
- var restore = function (stream, state) {
- state.tokenize = tokenize
- if (stream.pos < pos) {
- stream.pos = pos
- return style
- }
- return state.tokenize(stream, state)
- }
- return function (stream, state) {
- state.tokenize = restore
- return tokenize(stream, state)
- }
- }
- function maybeBackup(stream, state, pat, offset, style) {
- var cur = stream.current()
- var idx = cur.search(pat)
- if (idx > -1) {
- state.tokenize = backup(stream.pos, state.tokenize, style)
- stream.backUp(cur.length - idx - offset)
- }
- return style
- }
- function continueLine(state, column) {
- state.stack = {
- parent: state.stack,
- style: 'continuation',
- indented: column,
- tokenize: state.line,
- }
- state.line = state.tokenize
- }
- function finishContinue(state) {
- if (state.line == state.tokenize) {
- state.line = state.stack.tokenize
- state.stack = state.stack.parent
- }
- }
- function lineContinuable(column, tokenize) {
- return function (stream, state) {
- finishContinue(state)
- if (stream.match(/^\\$/)) {
- continueLine(state, column)
- return 'lineContinuation'
- }
- var style = tokenize(stream, state)
- if (stream.eol() && stream.current().match(/(?:^|[^\\])(?:\\\\)*\\$/)) {
- stream.backUp(1)
- }
- return style
- }
- }
- function commaContinuable(column, tokenize) {
- return function (stream, state) {
- finishContinue(state)
- var style = tokenize(stream, state)
- if (stream.eol() && stream.current().match(/,$/)) {
- continueLine(state, column)
- }
- return style
- }
- }
- function rubyInQuote(endQuote, tokenize) {
- // TODO: add multi line support
- return function (stream, state) {
- var ch = stream.peek()
- if (ch == endQuote && state.rubyState.tokenize.length == 1) {
- // step out of ruby context as it seems to complete processing all the braces
- stream.next()
- state.tokenize = tokenize
- return 'closeAttributeTag'
- } else {
- return ruby(stream, state)
- }
- }
- }
- function startRubySplat(tokenize) {
- var rubyState
- var runSplat = function (stream, state) {
- if (state.rubyState.tokenize.length == 1 && !state.rubyState.context.prev) {
- stream.backUp(1)
- if (stream.eatSpace()) {
- state.rubyState = rubyState
- state.tokenize = tokenize
- return tokenize(stream, state)
- }
- stream.next()
- }
- return ruby(stream, state)
- }
- return function (stream, state) {
- rubyState = state.rubyState
- state.rubyState = CodeMirror.startState(rubyMode)
- state.tokenize = runSplat
- return ruby(stream, state)
- }
- }
- function ruby(stream, state) {
- return rubyMode.token(stream, state.rubyState)
- }
- function htmlLine(stream, state) {
- if (stream.match(/^\\$/)) {
- return 'lineContinuation'
- }
- return html(stream, state)
- }
- function html(stream, state) {
- if (stream.match(/^#\{/)) {
- state.tokenize = rubyInQuote('}', state.tokenize)
- return null
- }
- return maybeBackup(stream, state, /[^\\]#\{/, 1, htmlMode.token(stream, state.htmlState))
- }
- function startHtmlLine(lastTokenize) {
- return function (stream, state) {
- var style = htmlLine(stream, state)
- if (stream.eol()) state.tokenize = lastTokenize
- return style
- }
- }
- function startHtmlMode(stream, state, offset) {
- state.stack = {
- parent: state.stack,
- style: 'html',
- indented: stream.column() + offset, // pipe + space
- tokenize: state.line,
- }
- state.line = state.tokenize = html
- return null
- }
- function comment(stream, state) {
- stream.skipToEnd()
- return state.stack.style
- }
- function commentMode(stream, state) {
- state.stack = {
- parent: state.stack,
- style: 'comment',
- indented: state.indented + 1,
- tokenize: state.line,
- }
- state.line = comment
- return comment(stream, state)
- }
- function attributeWrapper(stream, state) {
- if (stream.eat(state.stack.endQuote)) {
- state.line = state.stack.line
- state.tokenize = state.stack.tokenize
- state.stack = state.stack.parent
- return null
- }
- if (stream.match(wrappedAttributeNameRegexp)) {
- state.tokenize = attributeWrapperAssign
- return 'slimAttribute'
- }
- stream.next()
- return null
- }
- function attributeWrapperAssign(stream, state) {
- if (stream.match(/^==?/)) {
- state.tokenize = attributeWrapperValue
- return null
- }
- return attributeWrapper(stream, state)
- }
- function attributeWrapperValue(stream, state) {
- var ch = stream.peek()
- if (ch == '"' || ch == "'") {
- state.tokenize = readQuoted(ch, 'string', true, false, attributeWrapper)
- stream.next()
- return state.tokenize(stream, state)
- }
- if (ch == '[') {
- return startRubySplat(attributeWrapper)(stream, state)
- }
- if (stream.match(/^(true|false|nil)\b/)) {
- state.tokenize = attributeWrapper
- return 'keyword'
- }
- return startRubySplat(attributeWrapper)(stream, state)
- }
- function startAttributeWrapperMode(state, endQuote, tokenize) {
- state.stack = {
- parent: state.stack,
- style: 'wrapper',
- indented: state.indented + 1,
- tokenize: tokenize,
- line: state.line,
- endQuote: endQuote,
- }
- state.line = state.tokenize = attributeWrapper
- return null
- }
- function sub(stream, state) {
- if (stream.match(/^#\{/)) {
- state.tokenize = rubyInQuote('}', state.tokenize)
- return null
- }
- var subStream = new CodeMirror.StringStream(stream.string.slice(state.stack.indented), stream.tabSize)
- subStream.pos = stream.pos - state.stack.indented
- subStream.start = stream.start - state.stack.indented
- subStream.lastColumnPos = stream.lastColumnPos - state.stack.indented
- subStream.lastColumnValue = stream.lastColumnValue - state.stack.indented
- var style = state.subMode.token(subStream, state.subState)
- stream.pos = subStream.pos + state.stack.indented
- return style
- }
- function firstSub(stream, state) {
- state.stack.indented = stream.column()
- state.line = state.tokenize = sub
- return state.tokenize(stream, state)
- }
- function createMode(mode) {
- var query = embedded[mode]
- var spec = CodeMirror.mimeModes[query]
- if (spec) {
- return CodeMirror.getMode(config, spec)
- }
- var factory = CodeMirror.modes[query]
- if (factory) {
- return factory(config, { name: query })
- }
- return CodeMirror.getMode(config, 'null')
- }
- function getMode(mode) {
- if (!modes.hasOwnProperty(mode)) {
- return (modes[mode] = createMode(mode))
- }
- return modes[mode]
- }
- function startSubMode(mode, state) {
- var subMode = getMode(mode)
- var subState = CodeMirror.startState(subMode)
- state.subMode = subMode
- state.subState = subState
- state.stack = {
- parent: state.stack,
- style: 'sub',
- indented: state.indented + 1,
- tokenize: state.line,
- }
- state.line = state.tokenize = firstSub
- return 'slimSubmode'
- }
- function doctypeLine(stream, _state) {
- stream.skipToEnd()
- return 'slimDoctype'
- }
- function startLine(stream, state) {
- var ch = stream.peek()
- if (ch == '<') {
- return (state.tokenize = startHtmlLine(state.tokenize))(stream, state)
- }
- if (stream.match(/^[|']/)) {
- return startHtmlMode(stream, state, 1)
- }
- if (stream.match(/^\/(!|\[\w+])?/)) {
- return commentMode(stream, state)
- }
- if (stream.match(/^(-|==?[<>]?)/)) {
- state.tokenize = lineContinuable(stream.column(), commaContinuable(stream.column(), ruby))
- return 'slimSwitch'
- }
- if (stream.match(/^doctype\b/)) {
- state.tokenize = doctypeLine
- return 'keyword'
- }
- var m = stream.match(embeddedRegexp)
- if (m) {
- return startSubMode(m[1], state)
- }
- return slimTag(stream, state)
- }
- function slim(stream, state) {
- if (state.startOfLine) {
- return startLine(stream, state)
- }
- return slimTag(stream, state)
- }
- function slimTag(stream, state) {
- if (stream.eat('*')) {
- state.tokenize = startRubySplat(slimTagExtras)
- return null
- }
- if (stream.match(nameRegexp)) {
- state.tokenize = slimTagExtras
- return 'slimTag'
- }
- return slimClass(stream, state)
- }
- function slimTagExtras(stream, state) {
- if (stream.match(/^(<>?|><?)/)) {
- state.tokenize = slimClass
- return null
- }
- return slimClass(stream, state)
- }
- function slimClass(stream, state) {
- if (stream.match(classIdRegexp)) {
- state.tokenize = slimClass
- return 'slimId'
- }
- if (stream.match(classNameRegexp)) {
- state.tokenize = slimClass
- return 'slimClass'
- }
- return slimAttribute(stream, state)
- }
- function slimAttribute(stream, state) {
- if (stream.match(/^([\[\{\(])/)) {
- return startAttributeWrapperMode(state, closing[RegExp.$1], slimAttribute)
- }
- if (stream.match(attributeNameRegexp)) {
- state.tokenize = slimAttributeAssign
- return 'slimAttribute'
- }
- if (stream.peek() == '*') {
- stream.next()
- state.tokenize = startRubySplat(slimContent)
- return null
- }
- return slimContent(stream, state)
- }
- function slimAttributeAssign(stream, state) {
- if (stream.match(/^==?/)) {
- state.tokenize = slimAttributeValue
- return null
- }
- // should never happen, because of forward lookup
- return slimAttribute(stream, state)
- }
- function slimAttributeValue(stream, state) {
- var ch = stream.peek()
- if (ch == '"' || ch == "'") {
- state.tokenize = readQuoted(ch, 'string', true, false, slimAttribute)
- stream.next()
- return state.tokenize(stream, state)
- }
- if (ch == '[') {
- return startRubySplat(slimAttribute)(stream, state)
- }
- if (ch == ':') {
- return startRubySplat(slimAttributeSymbols)(stream, state)
- }
- if (stream.match(/^(true|false|nil)\b/)) {
- state.tokenize = slimAttribute
- return 'keyword'
- }
- return startRubySplat(slimAttribute)(stream, state)
- }
- function slimAttributeSymbols(stream, state) {
- stream.backUp(1)
- if (stream.match(/^[^\s],(?=:)/)) {
- state.tokenize = startRubySplat(slimAttributeSymbols)
- return null
- }
- stream.next()
- return slimAttribute(stream, state)
- }
- function readQuoted(quote, style, embed, unescaped, nextTokenize) {
- return function (stream, state) {
- finishContinue(state)
- var fresh = stream.current().length == 0
- if (stream.match(/^\\$/, fresh)) {
- if (!fresh) return style
- continueLine(state, state.indented)
- return 'lineContinuation'
- }
- if (stream.match(/^#\{/, fresh)) {
- if (!fresh) return style
- state.tokenize = rubyInQuote('}', state.tokenize)
- return null
- }
- var escaped = false,
- ch
- while ((ch = stream.next()) != null) {
- if (ch == quote && (unescaped || !escaped)) {
- state.tokenize = nextTokenize
- break
- }
- if (embed && ch == '#' && !escaped) {
- if (stream.eat('{')) {
- stream.backUp(2)
- break
- }
- }
- escaped = !escaped && ch == '\\'
- }
- if (stream.eol() && escaped) {
- stream.backUp(1)
- }
- return style
- }
- }
- function slimContent(stream, state) {
- if (stream.match(/^==?/)) {
- state.tokenize = ruby
- return 'slimSwitch'
- }
- if (stream.match(/^\/$/)) {
- // tag close hint
- state.tokenize = slim
- return null
- }
- if (stream.match(/^:/)) {
- // inline tag
- state.tokenize = slimTag
- return 'slimSwitch'
- }
- startHtmlMode(stream, state, 0)
- return state.tokenize(stream, state)
- }
- var mode = {
- // default to html mode
- startState: function () {
- var htmlState = CodeMirror.startState(htmlMode)
- var rubyState = CodeMirror.startState(rubyMode)
- return {
- htmlState: htmlState,
- rubyState: rubyState,
- stack: null,
- last: null,
- tokenize: slim,
- line: slim,
- indented: 0,
- }
- },
- copyState: function (state) {
- return {
- htmlState: CodeMirror.copyState(htmlMode, state.htmlState),
- rubyState: CodeMirror.copyState(rubyMode, state.rubyState),
- subMode: state.subMode,
- subState: state.subMode && CodeMirror.copyState(state.subMode, state.subState),
- stack: state.stack,
- last: state.last,
- tokenize: state.tokenize,
- line: state.line,
- }
- },
- token: function (stream, state) {
- if (stream.sol()) {
- state.indented = stream.indentation()
- state.startOfLine = true
- state.tokenize = state.line
- while (state.stack && state.stack.indented > state.indented && state.last != 'slimSubmode') {
- state.line = state.tokenize = state.stack.tokenize
- state.stack = state.stack.parent
- state.subMode = null
- state.subState = null
- }
- }
- if (stream.eatSpace()) return null
- var style = state.tokenize(stream, state)
- state.startOfLine = false
- if (style) state.last = style
- return styleMap.hasOwnProperty(style) ? styleMap[style] : style
- },
- blankLine: function (state) {
- if (state.subMode && state.subMode.blankLine) {
- return state.subMode.blankLine(state.subState)
- }
- },
- innerMode: function (state) {
- if (state.subMode) return { state: state.subState, mode: state.subMode }
- return { state: state, mode: mode }
- },
- //indent: function(state) {
- // return state.indented;
- //}
- }
- return mode
- },
- 'htmlmixed',
- 'ruby'
- )
- CodeMirror.defineMIME('text/x-slim', 'slim')
- CodeMirror.defineMIME('application/x-slim', 'slim')
- })
|