Skip to content

Instantly share code, notes, and snippets.

@jquense
Last active October 17, 2015 05:40
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jquense/265b46c740da4d20a718 to your computer and use it in GitHub Desktop.
Save jquense/265b46c740da4d20a718 to your computer and use it in GitHub Desktop.
An attempt at a jsx CodeMirror mode
/*global CodeMirror */
function indexOf(string, pattern, from) {
if (typeof pattern === "string") {
var found = string.indexOf(pattern, from);
return found;
}
var m = pattern.exec(from ? string.slice(from) : string);
return m ? (m.index + from) : -1;
}
function getAllIndexes(string, val, from) {
var indexes = [], i = from || 0;
for(; i < string.length; i++)
if (string[i] === val) indexes.push(i);
return indexes;
}
CodeMirror.defineMode("jsx", function(config, parserConfig) {
var jsMode = CodeMirror.getMode(config, "javascript");
var xmlMode = CodeMirror.getMode(config, { name: "xml", htmlMode: true });
var TAG = /<([\w_:\.-]*)/
, OPEN = '{', CLOSE = '}'
, foundTag = false;
function xmlToken(stream, state){
var oldContent = stream.string
, found = indexOf(oldContent, OPEN, stream.pos)
, pos;
if ( !state.xmlState)
state.xmlState = CodeMirror.startState(xmlMode, jsMode.indent ? jsMode.indent(state.jsState, '') : 0)
state.active = 'xml'
if ( found === stream.pos) {
stream.match(OPEN)
pos = stream.pos
state.active = 'js'
state.inJsExpression = true
// get past the attr value state
stream.string = oldContent.slice(found)
xmlMode.token(stream, state.xmlState)
stream.string = oldContent
stream.pos = pos
return 'jsx-bracket'
}
else if ( found !== -1)
stream.string = oldContent.slice(0, found)
var style = xmlMode.token(stream, state.xmlState)
if ( found !== -1) stream.string = oldContent
if ( !foundTag )
foundTag = !!state.xmlState.context
return style
}
function jsToken(stream, state){
var oldContent = stream.string
, found;
// this only sort of works, its an attempt to solve the problem of
// not knowing when a } is a ending a jsx expression (prop={}) or
// a js block/object
// example:
// <div style={{ width: 5 }} onClick={()=>{ throw Error() }}>
if (state.inJsExpression) {
var opens = getAllIndexes(oldContent, OPEN, stream.pos).length
, closes = getAllIndexes(oldContent, CLOSE, stream.pos)
found = opens > closes
? -1 : closes[closes.length - 1]
}
if (found === stream.pos) {
stream.match(CLOSE);
state.active = 'xml'
state.inJsExpression = false
return 'jsx-bracket';
}
else if ( found !== -1)
stream.string = oldContent.slice(0, found);
var style = jsMode.token(stream, state.jsState);
if (found > -1) stream.string = oldContent;
return style
}
return {
startState: function() {
return {
xmlState: CodeMirror.copyState(xmlMode, state.xmlState),
jsState: CodeMirror.startState(jsMode),
active: 'js',
inJsExpression: false
}
},
copyState: function(state) {
return {
jsState: CodeMirror.copyState(jsMode, state.jsState),
xmlState: CodeMirror.copyState(xmlMode, state.xmlState),
active: state.active,
inJsExpression: state.inJsExpression,
expressionCloseIndex: null // do not copy over
}
},
token: function(stream, state) {
var style;
if (state.active === 'js' ) {
if ( stream.match(TAG, false) ) {
style = xmlToken(stream, state)
}
else
style = jsToken(stream, state)
}
else {
if ( foundTag && state.xmlState.context == null) {
state.active = 'js'
foundTag = false
style = jsToken(stream, state)
}
else
style = xmlToken(stream, state)
}
return style
},
indent: function(state, textAfter) {
return state.active === 'js'
? jsMode.indent(state.jsState, textAfter)
: xmlMode.indent(state.xmlState, textAfter)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment