sass.js
330 lines
| 7.3 KiB
| application/javascript
|
JavascriptLexer
r4026 | CodeMirror.defineMode("sass", function(config) { | |||
var tokenRegexp = function(words){ | ||||
return new RegExp("^" + words.join("|")); | ||||
}; | ||||
var keywords = ["true", "false", "null", "auto"]; | ||||
var keywordsRegexp = new RegExp("^" + keywords.join("|")); | ||||
var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", "\\!=", "/", "\\*", "%", "and", "or", "not"]; | ||||
var opRegexp = tokenRegexp(operators); | ||||
var pseudoElementsRegexp = /^::?[\w\-]+/; | ||||
var urlTokens = function(stream, state){ | ||||
var ch = stream.peek(); | ||||
if (ch === ")"){ | ||||
stream.next(); | ||||
state.tokenizer = tokenBase; | ||||
return "operator"; | ||||
}else if (ch === "("){ | ||||
stream.next(); | ||||
stream.eatSpace(); | ||||
return "operator"; | ||||
}else if (ch === "'" || ch === '"'){ | ||||
state.tokenizer = buildStringTokenizer(stream.next()); | ||||
return "string"; | ||||
}else{ | ||||
state.tokenizer = buildStringTokenizer(")", false); | ||||
return "string"; | ||||
} | ||||
}; | ||||
var multilineComment = function(stream, state) { | ||||
if (stream.skipTo("*/")){ | ||||
stream.next(); | ||||
stream.next(); | ||||
state.tokenizer = tokenBase; | ||||
}else { | ||||
stream.next(); | ||||
} | ||||
return "comment"; | ||||
}; | ||||
var buildStringTokenizer = function(quote, greedy){ | ||||
if(greedy == null){ greedy = true; } | ||||
function stringTokenizer(stream, state){ | ||||
var nextChar = stream.next(); | ||||
var peekChar = stream.peek(); | ||||
var previousChar = stream.string.charAt(stream.pos-2); | ||||
var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\")); | ||||
/* | ||||
console.log("previousChar: " + previousChar); | ||||
console.log("nextChar: " + nextChar); | ||||
console.log("peekChar: " + peekChar); | ||||
console.log("ending: " + endingString); | ||||
*/ | ||||
if (endingString){ | ||||
if (nextChar !== quote && greedy) { stream.next(); } | ||||
state.tokenizer = tokenBase; | ||||
return "string"; | ||||
}else if (nextChar === "#" && peekChar === "{"){ | ||||
state.tokenizer = buildInterpolationTokenizer(stringTokenizer); | ||||
stream.next(); | ||||
return "operator"; | ||||
}else { | ||||
return "string"; | ||||
} | ||||
} | ||||
return stringTokenizer; | ||||
}; | ||||
var buildInterpolationTokenizer = function(currentTokenizer){ | ||||
return function(stream, state){ | ||||
if (stream.peek() === "}"){ | ||||
stream.next(); | ||||
state.tokenizer = currentTokenizer; | ||||
return "operator"; | ||||
}else{ | ||||
return tokenBase(stream, state); | ||||
} | ||||
}; | ||||
}; | ||||
var indent = function(state){ | ||||
if (state.indentCount == 0){ | ||||
state.indentCount++; | ||||
var lastScopeOffset = state.scopes[0].offset; | ||||
var currentOffset = lastScopeOffset + config.indentUnit; | ||||
state.scopes.unshift({ offset:currentOffset }); | ||||
} | ||||
}; | ||||
var dedent = function(state){ | ||||
if (state.scopes.length == 1) { return; } | ||||
state.scopes.shift(); | ||||
}; | ||||
var tokenBase = function(stream, state) { | ||||
var ch = stream.peek(); | ||||
// Single line Comment | ||||
if (stream.match('//')) { | ||||
stream.skipToEnd(); | ||||
return "comment"; | ||||
} | ||||
// Multiline Comment | ||||
if (stream.match('/*')){ | ||||
state.tokenizer = multilineComment; | ||||
return state.tokenizer(stream, state); | ||||
} | ||||
// Interpolation | ||||
if (stream.match('#{')){ | ||||
state.tokenizer = buildInterpolationTokenizer(tokenBase); | ||||
return "operator"; | ||||
} | ||||
if (ch === "."){ | ||||
stream.next(); | ||||
// Match class selectors | ||||
if (stream.match(/^[\w-]+/)){ | ||||
indent(state); | ||||
return "atom"; | ||||
}else if (stream.peek() === "#"){ | ||||
indent(state); | ||||
return "atom"; | ||||
}else{ | ||||
return "operator"; | ||||
} | ||||
} | ||||
if (ch === "#"){ | ||||
stream.next(); | ||||
// Hex numbers | ||||
if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ | ||||
return "number"; | ||||
} | ||||
// ID selectors | ||||
if (stream.match(/^[\w-]+/)){ | ||||
indent(state); | ||||
return "atom"; | ||||
} | ||||
if (stream.peek() === "#"){ | ||||
indent(state); | ||||
return "atom"; | ||||
} | ||||
} | ||||
// Numbers | ||||
if (stream.match(/^-?[0-9\.]+/)){ | ||||
return "number"; | ||||
} | ||||
// Units | ||||
if (stream.match(/^(px|em|in)\b/)){ | ||||
return "unit"; | ||||
} | ||||
if (stream.match(keywordsRegexp)){ | ||||
return "keyword"; | ||||
} | ||||
if (stream.match(/^url/) && stream.peek() === "("){ | ||||
state.tokenizer = urlTokens; | ||||
return "atom"; | ||||
} | ||||
// Variables | ||||
if (ch === "$"){ | ||||
stream.next(); | ||||
stream.eatWhile(/[\w-]/); | ||||
if (stream.peek() === ":"){ | ||||
stream.next(); | ||||
return "variable-2"; | ||||
}else{ | ||||
return "variable-3"; | ||||
} | ||||
} | ||||
if (ch === "!"){ | ||||
stream.next(); | ||||
if (stream.match(/^[\w]+/)){ | ||||
return "keyword"; | ||||
} | ||||
return "operator"; | ||||
} | ||||
if (ch === "="){ | ||||
stream.next(); | ||||
// Match shortcut mixin definition | ||||
if (stream.match(/^[\w-]+/)){ | ||||
indent(state); | ||||
return "meta"; | ||||
}else { | ||||
return "operator"; | ||||
} | ||||
} | ||||
if (ch === "+"){ | ||||
stream.next(); | ||||
// Match shortcut mixin definition | ||||
if (stream.match(/^[\w-]+/)){ | ||||
return "variable-3"; | ||||
}else { | ||||
return "operator"; | ||||
} | ||||
} | ||||
// Indent Directives | ||||
if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)){ | ||||
indent(state); | ||||
return "meta"; | ||||
} | ||||
// Other Directives | ||||
if (ch === "@"){ | ||||
stream.next(); | ||||
stream.eatWhile(/[\w-]/); | ||||
return "meta"; | ||||
} | ||||
// Strings | ||||
if (ch === '"' || ch === "'"){ | ||||
stream.next(); | ||||
state.tokenizer = buildStringTokenizer(ch); | ||||
return "string"; | ||||
} | ||||
// Pseudo element selectors | ||||
if (ch == ':' && stream.match(pseudoElementsRegexp)){ | ||||
return "keyword"; | ||||
} | ||||
// atoms | ||||
if (stream.eatWhile(/[\w-&]/)){ | ||||
// matches a property definition | ||||
if (stream.peek() === ":" && !stream.match(pseudoElementsRegexp, false)) | ||||
return "property"; | ||||
else | ||||
return "atom"; | ||||
} | ||||
if (stream.match(opRegexp)){ | ||||
return "operator"; | ||||
} | ||||
// If we haven't returned by now, we move 1 character | ||||
// and return an error | ||||
stream.next(); | ||||
return null; | ||||
}; | ||||
var tokenLexer = function(stream, state) { | ||||
if (stream.sol()){ | ||||
state.indentCount = 0; | ||||
} | ||||
var style = state.tokenizer(stream, state); | ||||
var current = stream.current(); | ||||
if (current === "@return"){ | ||||
dedent(state); | ||||
} | ||||
if (style === "atom"){ | ||||
indent(state); | ||||
} | ||||
if (style !== null){ | ||||
var startOfToken = stream.pos - current.length; | ||||
var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); | ||||
var newScopes = []; | ||||
for (var i = 0; i < state.scopes.length; i++){ | ||||
var scope = state.scopes[i]; | ||||
if (scope.offset <= withCurrentIndent){ | ||||
newScopes.push(scope); | ||||
} | ||||
} | ||||
state.scopes = newScopes; | ||||
} | ||||
return style; | ||||
}; | ||||
return { | ||||
startState: function() { | ||||
return { | ||||
tokenizer: tokenBase, | ||||
scopes: [{offset: 0, type: 'sass'}], | ||||
definedVars: [], | ||||
definedMixins: [] | ||||
}; | ||||
}, | ||||
token: function(stream, state) { | ||||
var style = tokenLexer(stream, state); | ||||
state.lastToken = { style: style, content: stream.current() }; | ||||
return style; | ||||
}, | ||||
indent: function(state) { | ||||
return state.scopes[0].offset; | ||||
} | ||||
}; | ||||
}); | ||||
CodeMirror.defineMIME("text/x-sass", "sass"); | ||||