// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
/***
|''Name''|tiddlywiki.js|
|''Description''|Enables TiddlyWikiy syntax highlighting using CodeMirror|
|''Author''|PMario|
|''Version''|0.1.7|
|''Status''|''stable''|
|''Source''|[[GitHub|https://github.com/pmario/CodeMirror2/blob/tw-syntax/mode/tiddlywiki]]|
|''Documentation''|https://codemirror.tiddlyspace.com/|
|''License''|[[MIT License|http://www.opensource.org/licenses/mit-license.php]]|
|''CoreVersion''|2.5.0|
|''Requires''|codemirror.js|
|''Keywords''|syntax highlighting color code mirror codemirror|
! Info
CoreVersion parameter is needed for TiddlyWiki only!
***/
;(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)
// Plain browser env
else mod(CodeMirror)
})(function (CodeMirror) {
'use strict'
CodeMirror.defineMode('tiddlywiki', function () {
// Tokenizer
var textwords = {}
var keywords = {
allTags: true,
closeAll: true,
list: true,
newJournal: true,
newTiddler: true,
permaview: true,
saveChanges: true,
search: true,
slider: true,
tabs: true,
tag: true,
tagging: true,
tags: true,
tiddler: true,
timeline: true,
today: true,
version: true,
option: true,
with: true,
filter: true,
}
var isSpaceName = /[\w_\-]/i,
reHR = /^\-\-\-\-+$/, //
reWikiCommentStart = /^\/\*\*\*$/, // /***
reWikiCommentStop = /^\*\*\*\/$/, // ***/
reBlockQuote = /^<<<$/,
reJsCodeStart = /^\/\/\{\{\{$/, // //{{{ js block start
reJsCodeStop = /^\/\/\}\}\}$/, // //}}} js stop
reXmlCodeStart = /^$/, // xml block start
reXmlCodeStop = /^$/, // xml stop
reCodeBlockStart = /^\{\{\{$/, // {{{ TW text div block start
reCodeBlockStop = /^\}\}\}$/, // }}} TW text stop
reUntilCodeStop = /.*?\}\}\}/
function chain(stream, state, f) {
state.tokenize = f
return f(stream, state)
}
function tokenBase(stream, state) {
var sol = stream.sol(),
ch = stream.peek()
state.block = false // indicates the start of a code block.
// check start of blocks
if (sol && /[<\/\*{}\-]/.test(ch)) {
if (stream.match(reCodeBlockStart)) {
state.block = true
return chain(stream, state, twTokenCode)
}
if (stream.match(reBlockQuote)) return 'quote'
if (stream.match(reWikiCommentStart) || stream.match(reWikiCommentStop)) return 'comment'
if (stream.match(reJsCodeStart) || stream.match(reJsCodeStop) || stream.match(reXmlCodeStart) || stream.match(reXmlCodeStop)) return 'comment'
if (stream.match(reHR)) return 'hr'
}
stream.next()
if (sol && /[\/\*!#;:>|]/.test(ch)) {
if (ch == '!') {
// tw header
stream.skipToEnd()
return 'header'
}
if (ch == '*') {
// tw list
stream.eatWhile('*')
return 'comment'
}
if (ch == '#') {
// tw numbered list
stream.eatWhile('#')
return 'comment'
}
if (ch == ';') {
// definition list, term
stream.eatWhile(';')
return 'comment'
}
if (ch == ':') {
// definition list, description
stream.eatWhile(':')
return 'comment'
}
if (ch == '>') {
// single line quote
stream.eatWhile('>')
return 'quote'
}
if (ch == '|') return 'header'
}
if (ch == '{' && stream.match('{{')) return chain(stream, state, twTokenCode)
// rudimentary html:// file:// link matching. TW knows much more ...
if (/[hf]/i.test(ch) && /[ti]/i.test(stream.peek()) && stream.match(/\b(ttps?|tp|ile):\/\/[\-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i)) return 'link'
// just a little string indicator, don't want to have the whole string covered
if (ch == '"') return 'string'
if (ch == '~')
// _no_ CamelCase indicator should be bold
return 'brace'
if (/[\[\]]/.test(ch) && stream.match(ch))
// check for [[..]]
return 'brace'
if (ch == '@') {
// check for space link. TODO fix @@...@@ highlighting
stream.eatWhile(isSpaceName)
return 'link'
}
if (/\d/.test(ch)) {
// numbers
stream.eatWhile(/\d/)
return 'number'
}
if (ch == '/') {
// tw invisible comment
if (stream.eat('%')) {
return chain(stream, state, twTokenComment)
} else if (stream.eat('/')) {
//
return chain(stream, state, twTokenEm)
}
}
if (ch == '_' && stream.eat('_'))
// tw underline
return chain(stream, state, twTokenUnderline)
// strikethrough and mdash handling
if (ch == '-' && stream.eat('-')) {
// if strikethrough looks ugly, change CSS.
if (stream.peek() != ' ') return chain(stream, state, twTokenStrike)
// mdash
if (stream.peek() == ' ') return 'brace'
}
if (ch == "'" && stream.eat("'"))
// tw bold
return chain(stream, state, twTokenStrong)
if (ch == '<' && stream.eat('<'))
// tw macro
return chain(stream, state, twTokenMacro)
// core macro handling
stream.eatWhile(/[\w\$_]/)
return textwords.propertyIsEnumerable(stream.current()) ? 'keyword' : null
}
// tw invisible comment
function twTokenComment(stream, state) {
var maybeEnd = false,
ch
while ((ch = stream.next())) {
if (ch == '/' && maybeEnd) {
state.tokenize = tokenBase
break
}
maybeEnd = ch == '%'
}
return 'comment'
}
// tw strong / bold
function twTokenStrong(stream, state) {
var maybeEnd = false,
ch
while ((ch = stream.next())) {
if (ch == "'" && maybeEnd) {
state.tokenize = tokenBase
break
}
maybeEnd = ch == "'"
}
return 'strong'
}
// tw code
function twTokenCode(stream, state) {
var sb = state.block
if (sb && stream.current()) {
return 'comment'
}
if (!sb && stream.match(reUntilCodeStop)) {
state.tokenize = tokenBase
return 'comment'
}
if (sb && stream.sol() && stream.match(reCodeBlockStop)) {
state.tokenize = tokenBase
return 'comment'
}
stream.next()
return 'comment'
}
// tw em / italic
function twTokenEm(stream, state) {
var maybeEnd = false,
ch
while ((ch = stream.next())) {
if (ch == '/' && maybeEnd) {
state.tokenize = tokenBase
break
}
maybeEnd = ch == '/'
}
return 'em'
}
// tw underlined text
function twTokenUnderline(stream, state) {
var maybeEnd = false,
ch
while ((ch = stream.next())) {
if (ch == '_' && maybeEnd) {
state.tokenize = tokenBase
break
}
maybeEnd = ch == '_'
}
return 'underlined'
}
// tw strike through text looks ugly
// change CSS if needed
function twTokenStrike(stream, state) {
var maybeEnd = false,
ch
while ((ch = stream.next())) {
if (ch == '-' && maybeEnd) {
state.tokenize = tokenBase
break
}
maybeEnd = ch == '-'
}
return 'strikethrough'
}
// macro
function twTokenMacro(stream, state) {
if (stream.current() == '<<') {
return 'macro'
}
var ch = stream.next()
if (!ch) {
state.tokenize = tokenBase
return null
}
if (ch == '>') {
if (stream.peek() == '>') {
stream.next()
state.tokenize = tokenBase
return 'macro'
}
}
stream.eatWhile(/[\w\$_]/)
return keywords.propertyIsEnumerable(stream.current()) ? 'keyword' : null
}
// Interface
return {
startState: function () {
return { tokenize: tokenBase }
},
token: function (stream, state) {
if (stream.eatSpace()) return null
var style = state.tokenize(stream, state)
return style
},
}
})
CodeMirror.defineMIME('text/x-tiddlywiki', 'tiddlywiki')
})