|
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../css/css")); else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../css/css"], mod); else // Plain browser env
mod(CodeMirror); })(function(CodeMirror) { "use strict";
CodeMirror.defineMode("sass", function(config) { var cssMode = CodeMirror.mimeModes["text/css"]; var propertyKeywords = cssMode.propertyKeywords || {}, colorKeywords = cssMode.colorKeywords || {}, valueKeywords = cssMode.valueKeywords || {}, fontProperties = cssMode.fontProperties || {};
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\-]*/;
var word;
function isEndLine(stream) { return !stream.peek() || stream.match(/\s+$/, false); }
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(); } if (isEndLine(stream)) { state.cursorHalf = 0; } 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 === "-") { if (stream.match(/^-\w+-/)) { return "meta"; } }
if (ch === ".") { stream.next(); if (stream.match(/^[\w-]+/)) { indent(state); return "qualifier"; } else if (stream.peek() === "#") { indent(state); return "tag"; } }
if (ch === "#") { stream.next(); // ID selectors
if (stream.match(/^[\w-]+/)) { indent(state); return "builtin"; } if (stream.peek() === "#") { indent(state); return "tag"; } }
// 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 "def"; }
// Other Directives
if (ch === "@") { stream.next(); stream.eatWhile(/[\w-]/); return "def"; }
if (stream.eatWhile(/[\w-]/)){ if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ word = stream.current().toLowerCase(); var prop = state.prevProp + "-" + word; if (propertyKeywords.hasOwnProperty(prop)) { return "property"; } else if (propertyKeywords.hasOwnProperty(word)) { state.prevProp = word; return "property"; } else if (fontProperties.hasOwnProperty(word)) { return "property"; } return "tag"; } else if(stream.match(/ *:/,false)){ indent(state); state.cursorHalf = 1; state.prevProp = stream.current().toLowerCase(); return "property"; } else if(stream.match(/ *,/,false)){ return "tag"; } else{ indent(state); return "tag"; } }
if(ch === ":"){ if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element
return "variable-3"; } 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 (isEndLine(stream)) { state.cursorHalf = 0; } return "number"; } }
// Numbers
if (stream.match(/^-?[0-9\.]+/)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "number"; }
// Units
if (stream.match(/^(px|em|in)\b/)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "unit"; }
if (stream.match(keywordsRegexp)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "keyword"; }
if (stream.match(/^url/) && stream.peek() === "(") { state.tokenizer = urlTokens; if (isEndLine(stream)) { state.cursorHalf = 0; } return "atom"; }
// Variables
if (ch === "$") { stream.next(); stream.eatWhile(/[\w-]/); if (isEndLine(stream)) { state.cursorHalf = 0; } return "variable-2"; }
// bang character for !important, !default, etc.
if (ch === "!") { stream.next(); state.cursorHalf = 0; return stream.match(/^[\w]+/) ? "keyword": "operator"; }
if (stream.match(opRegexp)){ if (isEndLine(stream)) { state.cursorHalf = 0; } return "operator"; }
// attributes
if (stream.eatWhile(/[\w-]/)) { if (isEndLine(stream)) { state.cursorHalf = 0; } word = stream.current().toLowerCase(); if (valueKeywords.hasOwnProperty(word)) { return "atom"; } else if (colorKeywords.hasOwnProperty(word)) { return "keyword"; } else if (propertyKeywords.hasOwnProperty(word)) { state.prevProp = stream.current().toLowerCase(); return "property"; } else { return "tag"; } }
//stream.eatSpace();
if (isEndLine(stream)) { 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; },
blockCommentStart: "/*", blockCommentEnd: "*/", lineComment: "//", fold: "indent" }; }, "css");
CodeMirror.defineMIME("text/x-sass", "sass");
});
|