text: // 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'), require('../css/css'));
    else if (typeof define === 'function' && define.amd)
      // AMD
      define(['../../lib/codemirror', '../css/css'], mod); // Plain browser env
    else mod(CodeMirror);
  })(CodeMirror => {
    CodeMirror.defineMode(
      'sass',
      config => {
        const cssMode = CodeMirror.mimeModes['text/css'];
        let propertyKeywords = cssMode.propertyKeywords || {},
          colorKeywords = cssMode.colorKeywords || {},
          valueKeywords = cssMode.valueKeywords || {},
          fontProperties = cssMode.fontProperties || {};
  
        function tokenRegexp(words) {
          return new RegExp(`^${words.join('|')}`);
        }
  
        const keywords = ['true', 'false', 'null', 'auto'];
        const keywordsRegexp = new RegExp(`^${keywords.join('|')}`);
  
        const operators = [
          '\\(',
          '\\)',
          '=',
          '>',
          '<',
          '==',
          '>=',
          '<=',
          '\\+',
          '-',
          '-',
          '\\!=',
          '/',
          '\\*',
          '%',
          'and',
          'or',
          'not',
          ';',
          '\\{',
          '\\}',
          ':',
        ];
        const opRegexp = tokenRegexp(operators);
  
        const pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/;
  
        let word;
  
        function isEndLine(stream) {
          return !stream.peek() || stream.match(/\s+$/, false);
        }
  
        function urlTokens(stream, state) {
          const 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';
          }
          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) {
            const nextChar = stream.next();
            const peekChar = stream.peek();
            const previousChar = stream.string.charAt(stream.pos - 2);
  
            const 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';
            }
            return 'string';
          }
  
          return stringTokenizer;
        }
  
        function buildInterpolationTokenizer(currentTokenizer) {
          return function(stream, state) {
            if (stream.peek() === '}') {
              stream.next();
              state.tokenizer = currentTokenizer;
              return 'operator';
            }
            return tokenBase(stream, state);
          };
        }
  
        function indent(state) {
          if (state.indentCount == 0) {
            state.indentCount++;
            const lastScopeOffset = state.scopes[0].offset;
            const 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) {
          const 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();
                const 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';
              }
  
              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';
            }
          } else {
            // cursorHalf===0 ends here
            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';
              }
              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;
          const style = state.tokenizer(stream, state);
          const current = stream.current();
  
          if (current === '@return' || current === '}') {
            dedent(state);
          }
  
          if (style !== null) {
            const startOfToken = stream.pos - current.length;
  
            const withCurrentIndent =
              startOfToken + config.indentUnit * state.indentCount;
  
            const newScopes = [];
  
            for (let i = 0; i < state.scopes.length; i++) {
              const scope = state.scopes[i];
  
              if (scope.offset <= withCurrentIndent) newScopes.push(scope);
            }
  
            state.scopes = newScopes;
          }
  
          return style;
        }
  
        return {
          startState() {
            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(stream, state) {
            const style = tokenLexer(stream, state);
  
            state.lastToken = { style, content: stream.current() };
  
            return style;
          },
  
          indent(state) {
            return state.scopes[0].offset;
          },
        };
      },
      'css',
    );
  
    CodeMirror.defineMIME('text/x-sass', 'sass');
  });
  