mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-26 09:31:34 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			455 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			455 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // CodeMirror, copyright (c) by Marijn Haverbeke and others
 | |
| // Distributed under an MIT license: https://codemirror.net/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;
 | |
|     }
 | |
|   };
 | |
| }, "css");
 | |
| 
 | |
| CodeMirror.defineMIME("text/x-sass", "sass");
 | |
| 
 | |
| });
 | 
