5
if (typeof exports == "object" && typeof module == "object")
6
mod(require("../../lib/codemirror"), require("../css/css"));
7
else if (typeof define == "function" && define.amd)
8
define(["../../lib/codemirror", "../css/css"], mod);
11
})(function(CodeMirror) {
14
CodeMirror.defineMode("sass", function(config) {
15
var cssMode = CodeMirror.mimeModes["text/css"];
16
var propertyKeywords = cssMode.propertyKeywords || {},
17
colorKeywords = cssMode.colorKeywords || {},
18
valueKeywords = cssMode.valueKeywords || {},
19
fontProperties = cssMode.fontProperties || {};
21
function tokenRegexp(words) {
22
return new RegExp("^" + words.join("|"));
25
var keywords = ["true", "false", "null", "auto"];
26
var keywordsRegexp = new RegExp("^" + keywords.join("|"));
28
var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-",
29
"\\!=", "/", "\\*", "%", "and", "or", "not", ";","\\{","\\}",":"];
30
var opRegexp = tokenRegexp(operators);
32
var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/;
36
function isEndLine(stream) {
37
return !stream.peek() || stream.match(/\s+$/, false);
40
function urlTokens(stream, state) {
41
var ch = stream.peek();
45
state.tokenizer = tokenBase;
47
} else if (ch === "(") {
52
} else if (ch === "'" || ch === '"') {
53
state.tokenizer = buildStringTokenizer(stream.next());
56
state.tokenizer = buildStringTokenizer(")", false);
60
function comment(indentation, multiLine) {
61
return function(stream, state) {
62
if (stream.sol() && stream.indentation() <= indentation) {
63
state.tokenizer = tokenBase;
64
return tokenBase(stream, state);
67
if (multiLine && stream.skipTo("*/")) {
70
state.tokenizer = tokenBase;
79
function buildStringTokenizer(quote, greedy) {
80
if (greedy == null) { greedy = true; }
82
function stringTokenizer(stream, state) {
83
var nextChar = stream.next();
84
var peekChar = stream.peek();
85
var previousChar = stream.string.charAt(stream.pos-2);
87
var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\"));
90
if (nextChar !== quote && greedy) { stream.next(); }
91
if (isEndLine(stream)) {
94
state.tokenizer = tokenBase;
96
} else if (nextChar === "#" && peekChar === "{") {
97
state.tokenizer = buildInterpolationTokenizer(stringTokenizer);
105
return stringTokenizer;
108
function buildInterpolationTokenizer(currentTokenizer) {
109
return function(stream, state) {
110
if (stream.peek() === "}") {
112
state.tokenizer = currentTokenizer;
115
return tokenBase(stream, state);
120
function indent(state) {
121
if (state.indentCount == 0) {
123
var lastScopeOffset = state.scopes[0].offset;
124
var currentOffset = lastScopeOffset + config.indentUnit;
125
state.scopes.unshift({ offset:currentOffset });
129
function dedent(state) {
130
if (state.scopes.length == 1) return;
132
state.scopes.shift();
135
function tokenBase(stream, state) {
136
var ch = stream.peek();
139
if (stream.match("/*")) {
140
state.tokenizer = comment(stream.indentation(), true);
141
return state.tokenizer(stream, state);
143
if (stream.match("//")) {
144
state.tokenizer = comment(stream.indentation(), false);
145
return state.tokenizer(stream, state);
149
if (stream.match("#{")) {
150
state.tokenizer = buildInterpolationTokenizer(tokenBase);
155
if (ch === '"' || ch === "'") {
157
state.tokenizer = buildStringTokenizer(ch);
161
if(!state.cursorHalf){
166
if (stream.match(/^-\w+-/)) {
173
if (stream.match(/^[\w-]+/)) {
176
} else if (stream.peek() === "#") {
185
if (stream.match(/^[\w-]+/)) {
189
if (stream.peek() === "#") {
198
stream.eatWhile(/[\w-]/);
203
if (stream.match(/^-?[0-9\.]+/))
207
if (stream.match(/^(px|em|in)\b/))
210
if (stream.match(keywordsRegexp))
213
if (stream.match(/^url/) && stream.peek() === "(") {
214
state.tokenizer = urlTokens;
220
if (stream.match(/^=[\w-]+/)) {
228
if (stream.match(/^\+[\w-]+/)){
234
if(stream.match('@extend')){
235
if(!stream.match(/\s*[\w]/))
242
if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
250
stream.eatWhile(/[\w-]/);
254
if (stream.eatWhile(/[\w-]/)){
255
if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){
256
word = stream.current().toLowerCase();
257
var prop = state.prevProp + "-" + word;
258
if (propertyKeywords.hasOwnProperty(prop)) {
260
} else if (propertyKeywords.hasOwnProperty(word)) {
261
state.prevProp = word;
263
} else if (fontProperties.hasOwnProperty(word)) {
268
else if(stream.match(/ *:/,false)){
270
state.cursorHalf = 1;
271
state.prevProp = stream.current().toLowerCase();
274
else if(stream.match(/ *,/,false)){
284
if (stream.match(pseudoElementsRegexp)){
298
if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){
299
if (isEndLine(stream)) {
300
state.cursorHalf = 0;
307
if (stream.match(/^-?[0-9\.]+/)){
308
if (isEndLine(stream)) {
309
state.cursorHalf = 0;
315
if (stream.match(/^(px|em|in)\b/)){
316
if (isEndLine(stream)) {
317
state.cursorHalf = 0;
322
if (stream.match(keywordsRegexp)){
323
if (isEndLine(stream)) {
324
state.cursorHalf = 0;
329
if (stream.match(/^url/) && stream.peek() === "(") {
330
state.tokenizer = urlTokens;
331
if (isEndLine(stream)) {
332
state.cursorHalf = 0;
340
stream.eatWhile(/[\w-]/);
341
if (isEndLine(stream)) {
342
state.cursorHalf = 0;
350
state.cursorHalf = 0;
351
return stream.match(/^[\w]+/) ? "keyword": "operator";
354
if (stream.match(opRegexp)){
355
if (isEndLine(stream)) {
356
state.cursorHalf = 0;
362
if (stream.eatWhile(/[\w-]/)) {
363
if (isEndLine(stream)) {
364
state.cursorHalf = 0;
366
word = stream.current().toLowerCase();
367
if (valueKeywords.hasOwnProperty(word)) {
369
} else if (colorKeywords.hasOwnProperty(word)) {
371
} else if (propertyKeywords.hasOwnProperty(word)) {
372
state.prevProp = stream.current().toLowerCase();
380
if (isEndLine(stream)) {
381
state.cursorHalf = 0;
387
if (stream.match(opRegexp))
396
function tokenLexer(stream, state) {
397
if (stream.sol()) state.indentCount = 0;
398
var style = state.tokenizer(stream, state);
399
var current = stream.current();
401
if (current === "@return" || current === "}"){
405
if (style !== null) {
406
var startOfToken = stream.pos - current.length;
408
var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount);
412
for (var i = 0; i < state.scopes.length; i++) {
413
var scope = state.scopes[i];
415
if (scope.offset <= withCurrentIndent)
416
newScopes.push(scope);
419
state.scopes = newScopes;
427
startState: function() {
429
tokenizer: tokenBase,
430
scopes: [{offset: 0, type: "sass"}],
438
token: function(stream, state) {
439
var style = tokenLexer(stream, state);
441
state.lastToken = { style: style, content: stream.current() };
446
indent: function(state) {
447
return state.scopes[0].offset;
450
blockCommentStart: "/*",
451
blockCommentEnd: "*/",
457
CodeMirror.defineMIME("text/x-sass", "sass");