Files @ 9dd726706178
Branch filter:

Location: kallithea/rhodecode/public/js/mode/sass/sass.js

Bradley M. Kuhn
Complete copyright notices for web interface; change footer to link to them.

The original copyright notice found in the footer was not accurate as it
included only one of the many copyright holders in this project. This change
creates an "about" page, which currently contains just the copyright and
license information. It links to repository for additional potential copyright
holders not listed on the about page.

Unlisted contributors are mentioned in template comments.

Html links for Kallithea is fixed and we link to Conservancy.

Display of version information in the footer is improved.
CodeMirror.defineMode("sass", function(config) {
  var tokenRegexp = function(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 = /^::?[\w\-]+/;

  var urlTokens = function(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";
    }
  };
  var multilineComment = function(stream, state) {
    if (stream.skipTo("*/")){
      stream.next();
      stream.next();
      state.tokenizer = tokenBase;
    }else {
      stream.next();
    }

    return "comment";
  };

  var buildStringTokenizer = function(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 !== "\\"));

      /*
      console.log("previousChar: " + previousChar);
      console.log("nextChar: " + nextChar);
      console.log("peekChar: " + peekChar);
      console.log("ending: " + endingString);
      */

      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;
  };

  var buildInterpolationTokenizer = function(currentTokenizer){
    return function(stream, state){
      if (stream.peek() === "}"){
        stream.next();
        state.tokenizer = currentTokenizer;
        return "operator";
      }else{
        return tokenBase(stream, state);
      }
    };
  };

  var indent = function(state){
    if (state.indentCount == 0){
      state.indentCount++;
      var lastScopeOffset = state.scopes[0].offset;
      var currentOffset = lastScopeOffset + config.indentUnit;
      state.scopes.unshift({ offset:currentOffset });
    }
  };

  var dedent = function(state){
    if (state.scopes.length == 1) { return; }

    state.scopes.shift();
  };

  var tokenBase = function(stream, state) {
    var ch = stream.peek();

    // Single line Comment
    if (stream.match('//')) {
      stream.skipToEnd();
      return "comment";
    }

    // Multiline Comment
    if (stream.match('/*')){
      state.tokenizer = multilineComment;
      return state.tokenizer(stream, state);
    }

    // Interpolation
    if (stream.match('#{')){
    state.tokenizer = buildInterpolationTokenizer(tokenBase);
      return "operator";
    }

    if (ch === "."){
      stream.next();

      // Match class selectors
      if (stream.match(/^[\w-]+/)){
        indent(state);
        return "atom";
      }else if (stream.peek() === "#"){
        indent(state);
        return "atom";
      }else{
        return "operator";
      }
    }

    if (ch === "#"){
      stream.next();

      // Hex numbers
      if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){
        return "number";
      }

      // ID selectors
      if (stream.match(/^[\w-]+/)){
        indent(state);
        return "atom";
      }

      if (stream.peek() === "#"){
        indent(state);
        return "atom";
      }
    }

    // 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";
    }

    // Variables
    if (ch === "$"){
      stream.next();
      stream.eatWhile(/[\w-]/);

      if (stream.peek() === ":"){
        stream.next();
        return "variable-2";
      }else{
        return "variable-3";
      }
    }

    if (ch === "!"){
      stream.next();

      if (stream.match(/^[\w]+/)){
        return "keyword";
      }

      return "operator";
    }

    if (ch === "="){
      stream.next();

      // Match shortcut mixin definition
      if (stream.match(/^[\w-]+/)){
        indent(state);
        return "meta";
      }else {
        return "operator";
      }
    }

    if (ch === "+"){
      stream.next();

      // Match shortcut mixin definition
      if (stream.match(/^[\w-]+/)){
        return "variable-3";
      }else {
        return "operator";
      }
    }

    // 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";
    }

    // Strings
    if (ch === '"' || ch === "'"){
      stream.next();
      state.tokenizer = buildStringTokenizer(ch);
      return "string";
    }

    // Pseudo element selectors
    if (ch == ':' && stream.match(pseudoElementsRegexp)){
      return "keyword";
    }

    // atoms
    if (stream.eatWhile(/[\w-&]/)){
      // matches a property definition
      if (stream.peek() === ":" && !stream.match(pseudoElementsRegexp, false))
        return "property";
      else
        return "atom";
    }

    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;
  };

  var tokenLexer = function(stream, state) {
    if (stream.sol()){
      state.indentCount = 0;
    }
    var style = state.tokenizer(stream, state);
    var current = stream.current();

    if (current === "@return"){
      dedent(state);
    }

    if (style === "atom"){
      indent(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'}],
        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");