| // CodeMirror, copyright (c) by Marijn Haverbeke and others |
| // Distributed under an MIT license: http://codemirror.net/LICENSE |
| |
| (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); |
| else // Plain browser env |
| mod(CodeMirror); |
| })(function(CodeMirror) { |
| "use strict"; |
| |
| CodeMirror.defineMode("sass", function(config) { |
| function tokenRegexp(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 = /^::?[a-zA-Z_][\w\-]*/; |
| |
| function urlTokens(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"; |
| } |
| } |
| function comment(indentation, multiLine) { |
| return function(stream, state) { |
| if (stream.sol() && stream.indentation() <= indentation) { |
| state.tokenizer = tokenBase; |
| return tokenBase(stream, state); |
| } |
| |
| if (multiLine && stream.skipTo("*/")) { |
| stream.next(); |
| stream.next(); |
| state.tokenizer = tokenBase; |
| } else { |
| stream.skipToEnd(); |
| } |
| |
| return "comment"; |
| }; |
| } |
| |
| function buildStringTokenizer(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 !== "\\")); |
| |
| 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; |
| } |
| |
| function buildInterpolationTokenizer(currentTokenizer) { |
| return function(stream, state) { |
| if (stream.peek() === "}") { |
| stream.next(); |
| state.tokenizer = currentTokenizer; |
| return "operator"; |
| } else { |
| return tokenBase(stream, state); |
| } |
| }; |
| } |
| |
| function indent(state) { |
| if (state.indentCount == 0) { |
| state.indentCount++; |
| var lastScopeOffset = state.scopes[0].offset; |
| var currentOffset = lastScopeOffset + config.indentUnit; |
| state.scopes.unshift({ offset:currentOffset }); |
| } |
| } |
| |
| function dedent(state) { |
| if (state.scopes.length == 1) return; |
| |
| state.scopes.shift(); |
| } |
| |
| function tokenBase(stream, state) { |
| var ch = stream.peek(); |
| |
| // Comment |
| if (stream.match("/*")) { |
| state.tokenizer = comment(stream.indentation(), true); |
| return state.tokenizer(stream, state); |
| } |
| if (stream.match("//")) { |
| state.tokenizer = comment(stream.indentation(), false); |
| return state.tokenizer(stream, state); |
| } |
| |
| // Interpolation |
| if (stream.match("#{")) { |
| state.tokenizer = buildInterpolationTokenizer(tokenBase); |
| return "operator"; |
| } |
| |
| // Strings |
| if (ch === '"' || ch === "'") { |
| stream.next(); |
| state.tokenizer = buildStringTokenizer(ch); |
| return "string"; |
| } |
| |
| if(!state.cursorHalf){// state.cursorHalf === 0 |
| // first half i.e. before : for key-value pairs |
| // including selectors |
| |
| if (ch === ".") { |
| stream.next(); |
| if (stream.match(/^[\w-]+/)) { |
| indent(state); |
| return "atom"; |
| } else if (stream.peek() === "#") { |
| indent(state); |
| return "atom"; |
| } |
| } |
| |
| if (ch === "#") { |
| stream.next(); |
| // ID selectors |
| if (stream.match(/^[\w-]+/)) { |
| indent(state); |
| return "atom"; |
| } |
| if (stream.peek() === "#") { |
| indent(state); |
| return "atom"; |
| } |
| } |
| |
| // Variables |
| if (ch === "$") { |
| stream.next(); |
| stream.eatWhile(/[\w-]/); |
| return "variable-2"; |
| } |
| |
| // 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"; |
| } |
| |
| if (ch === "=") { |
| // Match shortcut mixin definition |
| if (stream.match(/^=[\w-]+/)) { |
| indent(state); |
| return "meta"; |
| } |
| } |
| |
| if (ch === "+") { |
| // Match shortcut mixin definition |
| if (stream.match(/^\+[\w-]+/)){ |
| return "variable-3"; |
| } |
| } |
| |
| if(ch === "@"){ |
| if(stream.match(/@extend/)){ |
| if(!stream.match(/\s*[\w]/)) |
| dedent(state); |
| } |
| } |
| |
| |
| // 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"; |
| } |
| |
| if (stream.eatWhile(/[\w-]/)){ |
| if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ |
| return "property"; |
| } |
| else if(stream.match(/ *:/,false)){ |
| indent(state); |
| state.cursorHalf = 1; |
| return "atom"; |
| } |
| else if(stream.match(/ *,/,false)){ |
| return "atom"; |
| } |
| else{ |
| indent(state); |
| return "atom"; |
| } |
| } |
| |
| if(ch === ":"){ |
| if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element |
| return "keyword"; |
| } |
| stream.next(); |
| state.cursorHalf=1; |
| return "operator"; |
| } |
| |
| } // cursorHalf===0 ends here |
| else{ |
| |
| if (ch === "#") { |
| stream.next(); |
| // Hex numbers |
| if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "number"; |
| } |
| } |
| |
| // Numbers |
| if (stream.match(/^-?[0-9\.]+/)){ |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "number"; |
| } |
| |
| // Units |
| if (stream.match(/^(px|em|in)\b/)){ |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "unit"; |
| } |
| |
| if (stream.match(keywordsRegexp)){ |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "keyword"; |
| } |
| |
| if (stream.match(/^url/) && stream.peek() === "(") { |
| state.tokenizer = urlTokens; |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "atom"; |
| } |
| |
| // Variables |
| if (ch === "$") { |
| stream.next(); |
| stream.eatWhile(/[\w-]/); |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "variable-3"; |
| } |
| |
| // bang character for !important, !default, etc. |
| if (ch === "!") { |
| stream.next(); |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return stream.match(/^[\w]+/) ? "keyword": "operator"; |
| } |
| |
| if (stream.match(opRegexp)){ |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "operator"; |
| } |
| |
| // attributes |
| if (stream.eatWhile(/[\w-]/)) { |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| } |
| return "attribute"; |
| } |
| |
| //stream.eatSpace(); |
| if(!stream.peek()){ |
| state.cursorHalf = 0; |
| return null; |
| } |
| |
| } // else ends here |
| |
| 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; |
| } |
| |
| function tokenLexer(stream, state) { |
| if (stream.sol()) state.indentCount = 0; |
| var style = state.tokenizer(stream, state); |
| var current = stream.current(); |
| |
| if (current === "@return" || current === "}"){ |
| dedent(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"}], |
| indentCount: 0, |
| cursorHalf: 0, // cursor half tells us if cursor lies after (1) |
| // or before (0) colon (well... more or less) |
| 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"); |
| |
| }); |