diff --git a/mode/sass/index.html b/mode/sass/index.html index 9f4a79022101d18a660bb6d4d391786726205837..6305649e5cef3a8ea89dfc5657ce280aa5c717b3 100644 --- a/mode/sass/index.html +++ b/mode/sass/index.html @@ -7,6 +7,7 @@ <link rel="stylesheet" href="../../lib/codemirror.css"> <script src="../../lib/codemirror.js"></script> <script src="../../addon/edit/matchbrackets.js"></script> +<script src="../css/css.js"></script> <script src="sass.js"></script> <style>.CodeMirror {border: 1px solid #ddd; font-size:12px; height: 400px}</style> <div id=nav> @@ -58,7 +59,8 @@ body <script> var editor = CodeMirror.fromTextArea(document.getElementById("code"), { lineNumbers : true, - matchBrackets : true + matchBrackets : true, + mode: "sass" }); </script> diff --git a/mode/sass/sass.js b/mode/sass/sass.js index 6973ece292b09016a42197a81e3e0958ecd7de13..d2d25029681088739d48cb48b33d0997e1b7d7c0 100644 --- a/mode/sass/sass.js +++ b/mode/sass/sass.js @@ -3,19 +3,33 @@ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); + mod(require("../../lib/codemirror"), require("../css/css")); else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); + 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 || {}; + function tokenRegexp(words) { return new RegExp("^" + words.join("|")); } + function propWithVendorPrefix(keyset, string) { + if (string.indexOf('-') !== 0) { + return false; + } + + var unvendored = string.substring(string.indexOf("-", 1) + 1); + return keyset.hasOwnProperty(unvendored); + } + var keywords = ["true", "false", "null", "auto"]; var keywordsRegexp = new RegExp("^" + keywords.join("|")); @@ -25,6 +39,8 @@ CodeMirror.defineMode("sass", function(config) { var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/; + var word; + function urlTokens(stream, state) { var ch = stream.peek(); @@ -151,10 +167,10 @@ CodeMirror.defineMode("sass", function(config) { stream.next(); if (stream.match(/^[\w-]+/)) { indent(state); - return "atom"; + return "qualifier"; } else if (stream.peek() === "#") { indent(state); - return "atom"; + return "tag"; } } @@ -163,11 +179,11 @@ CodeMirror.defineMode("sass", function(config) { // ID selectors if (stream.match(/^[\w-]+/)) { indent(state); - return "atom"; + return "builtin"; } if (stream.peek() === "#") { indent(state); - return "atom"; + return "tag"; } } @@ -220,31 +236,42 @@ CodeMirror.defineMode("sass", function(config) { // Indent Directives if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) { indent(state); - return "meta"; + return "def"; } // Other Directives if (ch === "@") { stream.next(); stream.eatWhile(/[\w-]/); - return "meta"; + return "def"; } if (stream.eatWhile(/[\w-]/)){ if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ - return "property"; + 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 (propWithVendorPrefix(propertyKeywords, word)) { + return "property"; + } + return "tag"; } else if(stream.match(/ *:/,false)){ indent(state); state.cursorHalf = 1; - return "atom"; + state.prevProp = stream.current().toLowerCase(); + return "property"; } else if(stream.match(/ *,/,false)){ - return "atom"; + return "tag"; } else{ indent(state); - return "atom"; + return "tag"; } } @@ -309,20 +336,18 @@ CodeMirror.defineMode("sass", function(config) { if(!stream.peek()){ state.cursorHalf = 0; } - return "variable-3"; + return "variable-2"; } // bang character for !important, !default, etc. if (ch === "!") { stream.next(); - if(!stream.peek()){ - state.cursorHalf = 0; - } + state.cursorHalf = 0; return stream.match(/^[\w]+/) ? "keyword": "operator"; } if (stream.match(opRegexp)){ - if(!stream.peek()){ + if(!stream.peek() || stream.match(/\s+$/, false)){ state.cursorHalf = 0; } return "operator"; @@ -333,7 +358,17 @@ CodeMirror.defineMode("sass", function(config) { if(!stream.peek()){ state.cursorHalf = 0; } - return "attribute"; + 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(); diff --git a/mode/sass/test.js b/mode/sass/test.js new file mode 100644 index 0000000000000000000000000000000000000000..56ba006415e6e7b70980ee8cb4a6f8da8fa1fa75 --- /dev/null +++ b/mode/sass/test.js @@ -0,0 +1,103 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "sass"); + // Since Sass has an indent-based syntax, is almost impossible to test correctly the indentation in all cases. + // So disable it for tests. + mode.indent = undefined; + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT("comment", + "[comment // this is a comment]", + "[comment also this is a comment]") + + MT("comment_multiline", + "[comment /* this is a comment]", + "[comment also this is a comment]") + + MT("variable", + "[variable-2 $page-width][operator :] [number 800][unit px]") + + MT("global_attributes", + "[tag body]", + " [property font][operator :]", + " [property family][operator :] [atom sans-serif]", + " [property size][operator :] [number 30][unit em]", + " [property weight][operator :] [atom bold]") + + MT("scoped_styles", + "[builtin #contents]", + " [property width][operator :] [variable-2 $page-width]", + " [builtin #sidebar]", + " [property float][operator :] [atom right]", + " [property width][operator :] [variable-2 $sidebar-width]", + " [builtin #main]", + " [property width][operator :] [variable-2 $page-width] [operator -] [variable-2 $sidebar-width]", + " [property background][operator :] [variable-2 $primary-color]", + " [tag h2]", + " [property color][operator :] [keyword blue]") + + // Sass allows to write the colon as first char instead of a "separator". + // :color red + // Not supported + // MT("property_syntax", + // "[qualifier .foo]", + // " [operator :][property color] [keyword red]") + + MT("import", + "[def @import] [string \"sass/variables\"]", + // Probably it should parsed as above: as a string even without the " or ' + // "[def @import] [string sass/baz]" + "[def @import] [tag sass][operator /][tag baz]") + + MT("def", + "[def @if] [variable-2 $foo] [def @else]") + + MT("tag_on_more_lines", + "[tag td],", + "[tag th]", + " [property font-family][operator :] [string \"Arial\"], [atom serif]") + + MT("important", + "[qualifier .foo]", + " [property text-decoration][operator :] [atom none] [keyword !important]", + "[tag h1]", + " [property font-size][operator :] [number 2.5][unit em]") + + MT("selector", + // SCSS uses variable-3 + // "[tag h1]:[variable-3 before],", + // "[tag h2]:[variable-3 before]", + "[tag h1][keyword :before],", + "[tag h2][keyword :before]", + " [property content][operator :] [string \"::\"]") + + MT("definition_mixin_equal", + "[variable-2 $defined-bs-type][operator :] [atom border-box] [keyword !default]", + "[meta =bs][operator (][variable-2 $bs-type][operator :] [variable-2 $defined-bs-type][operator )]", + // The vendor prefix is highlighted as "meta" in CSS mode + // " [meta -webkit-][property box-sizing][operator :] [variable-2 $bs-type]", + " [property -webkit-box-sizing][operator :] [variable-2 $bs-type]", + " [property box-sizing][operator :] [variable-2 $bs-type]") + + MT("definition_mixin_with_space", + "[variable-2 $defined-bs-type][operator :] [atom border-box] [keyword !default]", + "[def @mixin] [tag bs][operator (][variable-2 $bs-type][operator :] [variable-2 $defined-bs-type][operator )] ", + // The vendor prefix is highlighted as "meta" in CSS mode + // " [meta -moz-][property box-sizing][operator :] [variable-2 $bs-type]", + " [property -moz-box-sizing][operator :] [variable-2 $bs-type]", + " [property box-sizing][operator :] [variable-2 $bs-type]") + + MT("numbers_start_dot_include_plus", + // The % is not highlighted correctly + // "[meta =button-links][operator (][variable-2 $button-base][operator :] [atom darken][operator (][variable-2 $color11], [number 10][unit %][operator )][operator )]", + "[meta =button-links][operator (][variable-2 $button-base][operator :] [atom darken][operator (][variable-2 $color11], [number 10][operator %))]", + " [property padding][operator :] [number .3][unit em] [number .6][unit em]", + " [variable-3 +border-radius][operator (][number 8][unit px][operator )]", + " [property background-color][operator :] [variable-2 $button-base]") + + MT("include", + "[qualifier .bar]", + " [def @include] [tag border-radius][operator (][number 8][unit px][operator )]") +})(); diff --git a/test/index.html b/test/index.html index 6ddf5b10219e76fb372b8e15ca2019d98bbfd055..deeb7781fbc0f25a7e5049dfc9de5a3a1dfe5a7e 100644 --- a/test/index.html +++ b/test/index.html @@ -31,6 +31,7 @@ <script src="../mode/python/python.js"></script> <script src="../mode/powershell/powershell.js"></script> <script src="../mode/ruby/ruby.js"></script> +<script src="../mode/sass/sass.js"></script> <script src="../mode/shell/shell.js"></script> <script src="../mode/slim/slim.js"></script> <script src="../mode/soy/soy.js"></script> @@ -121,6 +122,7 @@ <script src="../mode/php/test.js"></script> <script src="../mode/powershell/test.js"></script> <script src="../mode/ruby/test.js"></script> + <script src="../mode/sass/test.js"></script> <script src="../mode/shell/test.js"></script> <script src="../mode/slim/test.js"></script> <script src="../mode/soy/test.js"></script>