[gtksourceview] scss.lang, less.lang: Add new languages



commit 472515ec531ee0db4c6fdc3f83b8fa53886bf38b
Author: Jeffery To <jeffery to gmail com>
Date:   Wed May 30 02:54:58 2018 +0800

    scss.lang, less.lang: Add new languages
    
    SCSS (a newer syntax of Sass[1]) and Less[2] are two popular
    preprocessor style sheet languages that are interpreted or compiled into
    CSS. This adds language definitions for these two languages.
    
    This also depends on the extensibility changes in !1.
    
    [1] http://sass-lang.com/
    [2] http://lesscss.org/

 data/language-specs/Makefile.am     |   2 +
 data/language-specs/less.lang       | 793 ++++++++++++++++++++++++++++++++
 data/language-specs/scss.lang       | 878 ++++++++++++++++++++++++++++++++++++
 po/POTFILES.skip                    |   2 +
 tests/syntax-highlighting/file.less | 779 ++++++++++++++++++++++++++++++++
 tests/syntax-highlighting/file.scss | 805 +++++++++++++++++++++++++++++++++
 6 files changed, 3259 insertions(+)
---
diff --git a/data/language-specs/Makefile.am b/data/language-specs/Makefile.am
index aea1b608..6428f830 100644
--- a/data/language-specs/Makefile.am
+++ b/data/language-specs/Makefile.am
@@ -65,6 +65,7 @@ LANGUAGES =                   \
        julia.lang              \
        kotlin.lang             \
        latex.lang              \
+       less.lang               \
        lex.lang                \
        libtool.lang            \
        llvm.lang               \
@@ -110,6 +111,7 @@ LANGUAGES =                 \
        scala.lang              \
        scheme.lang             \
        scilab.lang             \
+       scss.lang               \
        sh.lang                 \
        sml.lang                \
        sparql.lang             \
diff --git a/data/language-specs/less.lang b/data/language-specs/less.lang
new file mode 100644
index 00000000..f8a4cd2b
--- /dev/null
+++ b/data/language-specs/less.lang
@@ -0,0 +1,793 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ This file is part of GtkSourceView
+
+ Author: Jeffery To <jeffery to gmail com>
+ Copyright (C) 2018 Jeffery To <jeffery to gmail com>
+
+ GtkSourceView is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GtkSourceView is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<language id="less" name="Less" version="2.0" _section="Other">
+  <metadata>
+    <property name="mimetypes">text/less;text/x-less</property>
+    <property name="globs">*.less</property>
+    <property name="line-comment-start">//</property>
+    <property name="block-comment-start">/*</property>
+    <property name="block-comment-end">*/</property>
+  </metadata>
+
+  <styles>
+
+    <!-- variables -->
+    <style id="variable"                    name="Variable"                    map-to="def:identifier"/>
+    <style id="built-in-variable"           name="Built-in Variable"           map-to="def:builtin"/>
+
+    <!-- operators -->
+    <style id="operator-symbol"             name="Operator Symbol"             map-to="css:symbol"/>
+
+    <!-- Less data types -->
+    <style id="boolean"                     name="Boolean Value"               map-to="def:boolean"/>
+    <style id="group-delimiter"             name="Group Delimiter"             map-to="css:delimiter"/>
+
+    <!-- mixins -->
+    <style id="mixin-parameters-delimiter"  name="Mixin Parameters Delimiter"  map-to="css:delimiter"/>
+
+    <!-- guards -->
+    <style id="guard-operator"              name="Guard Operator"              
map-to="css:at-rule-operator"/>
+
+    <!-- Less selectors -->
+    <style id="selector-fragment"           name="Selector Fragment"/>
+
+  </styles>
+
+  <default-regex-options case-sensitive="false"/>
+
+  <keyword-char-class>[a-z0-9_-]</keyword-char-class>
+
+  <definitions>
+
+    <!-- global -->
+
+    <define-regex id="statement-end" extended="true">
+      (?: ; | (?= \} ) )
+    </define-regex>
+
+    <context id="less-comment">
+      <include>
+        <context ref="def:c-like-comment"/>
+        <context ref="css:comment" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:comment" ref="less-comment"/>
+
+
+    <!-- variables -->
+
+    <define-regex id="variable-regex" extended="true">
+      (?: @ \%{css:identifier-regex} )
+    </define-regex>
+
+    <context id="variable" style-ref="variable">
+      <match>\%{variable-regex}</match>
+    </context>
+
+    <context id="variable-interpolation" style-ref="variable">
+      <start>@\{</start>
+      <end>\}</end>
+      <include>
+        <!-- nested interpolations are not documented but appear to work
+             (functions as variable reference / indirection) -->
+        <context ref="variable-interpolation-value"/>
+      </include>
+    </context>
+
+    <context id="variable-reference" style-ref="variable">
+      <match>@@\%{css:identifier-regex}</match>
+    </context>
+
+    <context id="property-variable" style-ref="variable">
+      <match>\$\%{css:identifier-regex}</match>
+    </context>
+
+    <context id="arguments-variable" style-ref="built-in-variable">
+      <match>@arguments\%]</match>
+    </context>
+
+    <context id="arguments-variable-interpolation" style-ref="built-in-variable">
+      <match>@\{arguments\}</match>
+    </context>
+
+    <context id="variable-value">
+      <include>
+        <context ref="arguments-variable"/>
+        <context ref="variable"/>
+        <context ref="variable-reference"/>
+        <context ref="property-variable"/>
+      </include>
+    </context>
+
+    <context id="variable-interpolation-value">
+      <include>
+        <context ref="arguments-variable-interpolation"/>
+        <context ref="variable-interpolation"/>
+      </include>
+    </context>
+
+
+    <!-- operators -->
+
+    <!-- it appears the slash is treated as division everywhere except
+         in a font property declaration and in an aspect ratio media query test,
+         not sure how to detect these cases
+         also not sure what are Less' rules regarding hyphen vs subtraction -->
+    <context id="arithmetic-operator" style-ref="operator-symbol">
+      <match extended="true">
+        (
+          [+*/] |
+          (?&lt;! \%{css:single-identifier-char-regex} )
+          -
+          (?! \%{css:single-identifier-char-regex} )
+        )
+      </match>
+    </context>
+
+
+    <!-- Less data types -->
+
+    <context id="boolean" style-ref="boolean">
+      <keyword>true</keyword>
+    </context>
+
+    <context id="double-quoted-escape-string" style-ref="css:string" end-at-line-end="true" class="string" 
class-disabled="no-spell-check">
+      <start>~"</start>
+      <end>"</end>
+      <include>
+        <context ref="css:string-content"/>
+      </include>
+    </context>
+
+    <context id="single-quoted-escape-string" style-ref="css:string" end-at-line-end="true" class="string" 
class-disabled="no-spell-check">
+      <start>~'</start>
+      <end>'</end>
+      <include>
+        <context ref="css:string-content"/>
+      </include>
+    </context>
+
+    <context id="escape-string">
+      <include>
+        <context ref="double-quoted-escape-string"/>
+        <context ref="single-quoted-escape-string"/>
+      </include>
+    </context>
+
+    <context id="detached-ruleset">
+      <start>\{</start>
+      <end>\}</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:block-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="css:block-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:style-block-content"/>
+      </include>
+    </context>
+
+    <context id="detached-ruleset-call-close-paren" style-ref="variable">
+      <match>\)</match>
+    </context>
+
+    <context id="detached-ruleset-call">
+      <start>\%{variable-regex}\(</start>
+      <end>\%{statement-end}</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="variable"/>
+        <context sub-pattern="0" where="end" style-ref="css:delimiter"/>
+        <!-- no comments allowed -->
+        <context ref="detached-ruleset-call-close-paren"/>
+      </include>
+    </context>
+
+    <context id="data-group">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:data-value"/>
+      </include>
+    </context>
+
+    <context id="any-group">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+      </include>
+    </context>
+
+
+    <!-- data types -->
+
+    <context id="less-string-content">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:string-content" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:string-content" ref="less-string-content"/>
+
+
+    <!-- Less functions -->
+
+    <!-- since % isn't a valid identifier -->
+    <context id="format">
+      <start>%\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:function"/>
+        <context sub-pattern="0" where="end" style-ref="css:function"/>
+        <context ref="css:comment"/>
+        <context ref="css:function-content"/>
+      </include>
+    </context>
+
+
+    <!-- functions -->
+
+    <context id="less-url">
+      <start>url\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:function"/>
+        <context sub-pattern="0" where="end" style-ref="css:function"/>
+        <!-- only accept multi-line comments because // is part of urls -->
+        <context ref="css:comment" original="true"/>
+        <context ref="css:string-value"/>
+      </include>
+    </context>
+
+    <context id="less-function-content">
+      <include>
+        <context ref="css:function-content" original="true"/>
+        <context ref="css:semicolon"/> <!-- allowed as argument separator -->
+      </include>
+    </context>
+
+    <context id="less-function-call">
+      <include>
+        <context ref="format"/>
+        <context ref="css:function-call" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:url" ref="less-url"/>
+    <replace id="css:function-content" ref="less-function-content"/>
+    <replace id="css:function-call" ref="less-function-call"/>
+
+
+    <!-- data values -->
+
+    <context id="less-name-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="variable-value"/>
+        <context ref="escape-string"/> <!-- outputs unquoted strings -->
+        <context ref="css:name-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-string-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="variable-value"/>
+        <context ref="css:string-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-data-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="data-group"/>
+        <context ref="variable-value"/>
+        <context ref="escape-string"/>
+        <context ref="css:string-value" original="true"/>
+        <context ref="css:color-value"/>
+        <context ref="css:number-value"/>
+        <context ref="css:unicode-range"/>
+        <context ref="arithmetic-operator"/>
+      </include>
+    </context>
+
+    <replace id="css:name-value" ref="less-name-value"/>
+    <replace id="css:string-value" ref="less-string-value"/>
+    <replace id="css:data-value" ref="less-data-value"/>
+
+
+    <!-- any assignable value -->
+
+    <context id="less-any-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="any-group"/>
+        <context ref="variable-value"/>
+        <context ref="escape-string"/>
+        <context ref="boolean"/>
+        <context ref="detached-ruleset"/>
+        <context ref="css:property-value-keyword"/>
+        <context ref="css:string-value" original="true"/>
+        <context ref="css:color-value"/>
+        <context ref="css:number-value"/>
+        <context ref="css:unicode-range"/>
+        <context ref="arithmetic-operator"/>
+        <context ref="css:slash"/>
+        <context ref="css:comma"/>
+      </include>
+    </context>
+
+    <replace id="css:any-value" ref="less-any-value"/>
+
+
+    <!-- style properties -->
+
+    <context id="less-property-name">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:property-name" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:property-name" ref="less-property-name"/>
+
+
+    <!-- style block -->
+
+    <context id="less-declaration-property">
+      <include>
+        <context ref="variable"/> <!-- variable assignment -->
+        <context ref="css:declaration-property" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-declaration-value">
+      <start extended="true">
+        (?(DEFINE)
+          (?&lt;interpolation&gt;  # recursive subpattern to find matching brackets
+            @\{
+              (?:
+                (?>
+                  (?:
+                    [^@{}]+ |
+                    (?! @\{ | \} ) .
+                  )+
+                ) |
+                (?&amp;interpolation)
+              )*
+            \}
+          )
+        )
+        (
+          \+_?: |  # property merge
+          :
+          (?:
+            (?!                                       # not the start of a
+              \%{css:single-identifier-char-regex} |  #   pseudo-class
+              [:\\] |                                 #   pseudo-element, escape
+              @\{                                     #   variable interpolation
+            ) |                                       # or
+            (?=                                       # ends like a normal declaration
+              (?&gt;
+                (?:
+                  [^;}{@]+ |
+                  (?&amp;interpolation)+ |
+                  \@+
+                )*
+              )
+              \%{css:declaration-value-end}           #   with a semicolon or at the end of a block
+            )
+          )
+        )
+      </start>
+      <end>\%{css:declaration-value-end}</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:declaration-value-content"/>
+      </include>
+    </context>
+
+    <context id="less-style-block-content">
+      <include>
+        <context ref="css:at-rule"/> <!-- because Less variables look like at-rules -->
+        <context ref="detached-ruleset-call"/>
+        <context ref="standalone-plugin-function-call"/>
+        <context ref="inside-ruleset-extend"/>
+        <context ref="css:style-block-content" original="true"/>
+        <context ref="css:selector"/>
+        <context ref="css:style-block"/>
+      </include>
+    </context>
+
+    <replace id="css:declaration-property" ref="less-declaration-property"/>
+    <replace id="css:declaration-value" ref="less-declaration-value"/>
+    <replace id="css:style-block-content" ref="less-style-block-content"/>
+
+
+    <!-- media queries -->
+
+    <!-- include variable-value at this level because a variable can
+         contain the whole media feature test,
+         e.g. ~'(orientation: landscape)'
+         allowing variable-value here means variables are also allowed
+         for media type and media feature test name/value -->
+    <context id="less-media-queries">
+      <include>
+        <context ref="variable-value"/>
+        <context ref="css:media-queries" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:media-queries" ref="less-media-queries"/>
+
+
+    <!-- Less at-rules -->
+
+    <!--
+    @plugin <options>? <url(...)|"url">;
+    -->
+
+    <context id="at-plugin-options">
+      <start>(?&lt;=@plugin)\s*(\()</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="1" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <!-- options are passed to the plugin directly, not parsed by Less -->
+      </include>
+    </context>
+
+    <context id="at-plugin">
+      <start>@plugin\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="at-plugin-options"/>
+        <context ref="css:url"/>
+        <!-- appears to follow the same rules as @import regarding variables -->
+        <context ref="escape-string"/>
+        <context ref="css:string-value" original="true"/>
+        <context ref="css:at-rule-delimiter"/>
+      </include>
+    </context>
+
+    <context id="standalone-plugin-function-call">
+      <start>(?=\%{css:identifier-regex}\()</start>
+      <end>\%{statement-end}</end>
+      <include>
+        <context sub-pattern="0" where="end" style-ref="css:delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:function-call"/>
+      </include>
+    </context>
+
+
+    <!-- at-rules -->
+
+    <context id="less-font-feature-type-value">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:font-feature-type-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-font-feature-value-declaration-name">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:font-feature-value-declaration-name" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-font-feature-value-declaration-value-content">
+      <include>
+        <context ref="variable-value"/>
+        <context ref="css:font-feature-value-declaration-value-content" original="true"/>
+      </include>
+    </context>
+
+    <!--
+    @import <option (, option)*>? <url(...)|"url"> <media-queries>?;
+    -->
+
+    <context id="less-at-import-options-keyword" style-ref="css:keyword">
+      <keyword>css</keyword>
+      <keyword>inline</keyword>
+      <keyword>less</keyword>
+      <keyword>multiple</keyword>
+      <keyword>once</keyword>
+      <keyword>optional</keyword>
+      <keyword>reference</keyword>
+    </context>
+
+    <context id="less-at-import-options">
+      <start>(?&lt;=@import)\s*(\()</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="1" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="less-at-import-options-keyword"/>
+        <context ref="css:comma"/>
+      </include>
+    </context>
+
+    <context id="less-at-import">
+      <start>@import\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="less-at-import-options"/>
+        <context ref="css:url"/>
+        <context ref="css:media-queries"/>
+        <!--
+        it appears only variable interpolation (in strings) is allowed
+        https://github.com/SomMeri/less4j/wiki/Less-Language-Import#syntax
+        but variables are allowed in media queries :-P
+        -->
+        <context ref="css:string-value" original="true"/>
+        <context ref="css:at-rule-delimiter"/>
+      </include>
+    </context>
+
+    <context id="less-keyframe-selector-value">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:keyframe-selector-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-at-rule">
+      <include>
+        <context ref="at-plugin"/>
+        <context ref="css:at-rule" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:at-rule-style-block-content" ref="less-style-block-content"/>
+    <replace id="css:at-rule-css-block-content" ref="less-style-block-content"/>
+    <replace id="css:font-feature-type-value" ref="less-font-feature-type-value"/>
+    <replace id="css:font-feature-value-declaration-name" ref="less-font-feature-value-declaration-name"/>
+    <replace id="css:font-feature-value-declaration-value-content" 
ref="less-font-feature-value-declaration-value-content"/>
+    <replace id="css:at-import" ref="less-at-import"/>
+    <replace id="css:keyframe-selector-value" ref="less-keyframe-selector-value"/>
+    <replace id="css:at-rule" ref="less-at-rule"/>
+
+
+    <!-- mixins -->
+
+    <context id="variable-arguments" style-ref="operator-symbol">
+      <match>\.\.\.</match>
+    </context>
+
+    <context id="mixin-parameters">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="mixin-parameters-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="mixin-parameters-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="variable-arguments"/>
+        <context ref="css:colon"/> <!-- named parameters / default values -->
+        <context ref="css:semicolon"/>
+      </include>
+    </context>
+
+
+    <!-- guards -->
+
+    <context id="guard-logical-operator" style-ref="guard-operator">
+      <keyword>and</keyword>
+      <keyword>not</keyword>
+      <keyword>or</keyword>
+    </context>
+
+    <context id="guard-comparison-operator" style-ref="operator-symbol">
+      <match>(&gt;=?|=&lt;?|&lt;)</match>
+    </context>
+
+    <context id="guard-test">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:test-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="css:test-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="guard-comparison-operator"/>
+      </include>
+    </context>
+
+    <context id="guard">
+      <start>\%[when\%]</start>
+      <end>(?=\{)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="guard-operator"/>
+        <context ref="css:comment"/>
+        <context ref="guard-test"/>
+        <context ref="guard-logical-operator"/>
+        <context ref="css:comma"/>
+      </include>
+    </context>
+
+
+    <!-- Less selectors -->
+
+    <context id="parent-combinator">
+      <match>(&amp;)(\%{css:identifier-chars-regex}?)</match>
+      <include>
+        <context sub-pattern="1" style-ref="css:combinator"/>
+        <context sub-pattern="2" style-ref="selector-fragment"/>
+      </include>
+    </context>
+
+    <context id="variable-interpolation-fragment" style-ref="selector-fragment">
+      <match>(?&lt;=\})\%{css:identifier-chars-regex}</match>
+    </context>
+
+    <context id="less-pseudo-classes" style-ref="css:pseudo-class">
+      <prefix>:</prefix>
+      <keyword>extend</keyword>
+    </context>
+
+    <context id="extend-pseudo-class-argument-keyword" style-ref="css:keyword">
+      <keyword>all</keyword>
+    </context>
+
+    <context id="extend-pseudo-class-argument">
+      <start>(?&lt;=:extend)\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:pseudo-class"/>
+        <context sub-pattern="0" where="end" style-ref="css:pseudo-class"/>
+        <context ref="css:comment"/>
+        <context ref="extend-pseudo-class-argument-keyword"/>
+        <context ref="css:selector"/>
+      </include>
+    </context>
+
+    <context id="inside-ruleset-extend">
+      <start>(?=&amp;:extend\()</start>
+      <end>\%{statement-end}</end>
+      <include>
+        <context sub-pattern="0" where="end" style-ref="css:delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:selector"/>
+      </include>
+    </context>
+
+
+    <!-- selectors -->
+
+    <context id="less-attribute-selector-content">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:attribute-selector-content" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-simple-selector">
+      <include>
+        <context ref="variable-interpolation-value"/> <!-- include in simple selector to be included in 
:not() -->
+        <context ref="variable-interpolation-fragment"/>
+        <context ref="css:simple-selector" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-combinator">
+      <include>
+        <context ref="parent-combinator"/>
+        <context ref="css:combinator" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-pseudo-class">
+      <include>
+        <context ref="less-pseudo-classes"/>
+        <context ref="css:pseudo-class" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-lang-pseudo-class-argument-content">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:lang-pseudo-class-argument-content" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-nth-pseudo-class-argument-content">
+      <include>
+        <context ref="variable-interpolation-value"/>
+        <context ref="css:nth-pseudo-class-argument-content" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-pseudo-class-argument">
+      <include>
+        <context ref="extend-pseudo-class-argument"/>
+        <context ref="css:pseudo-class-argument" original="true"/>
+      </include>
+    </context>
+
+    <context id="less-selector">
+      <include>
+        <context ref="guard"/>
+        <context ref="css:modifier"/>
+        <context ref="css:selector" original="true"/>
+        <context ref="mixin-parameters"/> <!-- can interfere with pseudo-class arguments -->
+        <context ref="css:semicolon"/> <!-- after mixin calls -->
+      </include>
+    </context>
+
+    <replace id="css:attribute-selector-content" ref="less-attribute-selector-content"/>
+    <replace id="css:simple-selector" ref="less-simple-selector"/>
+    <replace id="css:combinator" ref="less-combinator"/>
+    <replace id="css:pseudo-class" ref="less-pseudo-class"/>
+    <replace id="css:lang-pseudo-class-argument-content" ref="less-lang-pseudo-class-argument-content"/>
+    <replace id="css:nth-pseudo-class-argument-content" ref="less-nth-pseudo-class-argument-content"/>
+    <replace id="css:pseudo-class-argument" ref="less-pseudo-class-argument"/>
+    <replace id="css:selector" ref="less-selector"/>
+
+
+    <!-- top level declarations -->
+
+    <context id="top-level-declaration-property">
+      <include>
+        <context ref="variable"/>
+      </include>
+    </context>
+
+    <context id="top-level-declaration">
+      <include>
+        <context ref="top-level-declaration-property"/>
+        <context ref="css:declaration-value"/>
+        <context ref="css:modifier"/>
+        <context ref="css:semicolon"/>
+      </include>
+    </context>
+
+
+    <!-- main context -->
+
+    <context id="less" class="no-spell-check">
+      <include>
+        <context ref="css:comment"/>
+        <context ref="css:at-rule"/> <!-- because Less variables look like at-rules -->
+        <context ref="detached-ruleset-call"/>
+        <context ref="standalone-plugin-function-call"/>
+        <context ref="top-level-declaration"/>
+        <context ref="css:selector"/>
+        <context ref="css:style-block"/>
+      </include>
+    </context>
+
+  </definitions>
+</language>
diff --git a/data/language-specs/scss.lang b/data/language-specs/scss.lang
new file mode 100644
index 00000000..4320c486
--- /dev/null
+++ b/data/language-specs/scss.lang
@@ -0,0 +1,878 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ This file is part of GtkSourceView
+
+ Author: Jeffery To <jeffery to gmail com>
+ Copyright (C) 2018 Jeffery To <jeffery to gmail com>
+
+ GtkSourceView is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ GtkSourceView is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<language id="scss" name="SCSS" version="2.0" _section="Other">
+  <metadata>
+    <property name="mimetypes">text/x-scss</property>
+    <property name="globs">*.scss</property>
+    <property name="line-comment-start">//</property>
+    <property name="block-comment-start">/*</property>
+    <property name="block-comment-end">*/</property>
+  </metadata>
+
+  <styles>
+
+    <!-- interpolations -->
+    <style id="interpolation"               name="Interpolation"               map-to="def:preprocessor"/>
+
+    <!-- variables -->
+    <style id="variable"                    name="Variable"                    map-to="def:identifier"/>
+
+    <!-- operators -->
+    <style id="operator-symbol"             name="Operator Symbol"             map-to="css:symbol"/>
+    <style id="logical-operator"            name="Logical Operator"            map-to="def:preprocessor"/>
+
+    <!-- Sass data types -->
+    <style id="boolean"                     name="Boolean Value"               map-to="def:boolean"/>
+    <style id="null"                        name="Null Value"                  
map-to="def:special-constant"/>
+    <style id="list-delimiter"              name="List Delimiter"              map-to="css:delimiter"/>
+    <style id="group-delimiter"             name="Group Delimiter"             map-to="css:delimiter"/>
+
+    <!-- Sass selectors -->
+    <style id="placeholder-selector"        name="Placeholder Selector"        map-to="def:identifier"/>
+    <style id="selector-fragment"           name="Selector Fragment"/>
+
+    <!-- Sass at-rules -->
+    <style id="mixin-name"                  name="Mixin Name"                  map-to="def:keyword"/>
+
+  </styles>
+
+  <default-regex-options case-sensitive="false"/>
+
+  <keyword-char-class>[a-z0-9_-]</keyword-char-class>
+
+  <definitions>
+
+    <!-- global -->
+
+    <context id="sass-c-like-comment-multiline" style-ref="def:comment" class-disabled="no-spell-check" 
class="comment">
+      <start>/\*</start>
+      <end>\*/</end>
+      <include>
+        <context ref="interpolation"/>
+        <context ref="def:in-comment"/>
+      </include>
+    </context>
+
+    <context id="scss-comment">
+      <include>
+        <context ref="def:c-like-comment"/>
+        <context ref="sass-c-like-comment-multiline"/>
+        <context ref="def:c-like-close-comment-outside-comment"/>
+      </include>
+    </context>
+
+    <replace id="css:comment" ref="scss-comment"/>
+
+
+    <!-- interpolations -->
+
+    <context id="interpolation">
+      <start>#\{</start>
+      <end>\}</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="interpolation"/>
+        <context sub-pattern="0" where="end" style-ref="interpolation"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+      </include>
+    </context>
+
+
+    <!-- variables -->
+
+    <context id="variable" style-ref="variable">
+      <match>\$\%{css:identifier-regex}</match>
+    </context>
+
+
+    <!-- operators -->
+
+    <!-- leave out the division operator (/)
+         as we cannot reliably distinguish between a literal slash and
+         a division operation -->
+    <context id="arithmetic-operator" style-ref="operator-symbol">
+      <match extended="true">
+        (
+          [+*%] |
+          (?&lt;! \%{css:single-identifier-char-regex} )
+          -
+          (?! \%{css:single-identifier-char-regex} )
+        )
+      </match>
+    </context>
+
+    <context id="string-operator" style-ref="operator-symbol">
+      <match>\+</match>
+    </context>
+
+    <context id="comparison-operator" style-ref="operator-symbol">
+      <match>(&lt;=?|&gt;=?|[=!]=)</match>
+    </context>
+
+    <context id="logical-operator" style-ref="logical-operator">
+      <keyword>and</keyword>
+      <keyword>not</keyword>
+      <keyword>or</keyword>
+    </context>
+
+
+    <!-- Sass data types -->
+
+    <context id="boolean" style-ref="boolean">
+      <keyword>false</keyword>
+      <keyword>true</keyword>
+    </context>
+
+    <context id="null" style-ref="null">
+      <keyword>null</keyword>
+    </context>
+
+    <context id="parent-selector-list" style-ref="css:combinator">
+      <match>&amp;</match>
+    </context>
+
+    <context id="bracketed-list">
+      <start>\[</start>
+      <end>\]</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="list-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="list-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+      </include>
+    </context>
+
+    <!-- not sure why one would use a string group but it appears to be syntactically valid -->
+    <context id="string-group">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:string-value"/>
+      </include>
+    </context>
+
+    <context id="data-group">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:data-value"/>
+      </include>
+    </context>
+
+    <!--
+    this could be a list, a map, or an order of operations grouping
+    not sure how to differentiate between these
+    -->
+    <context id="any-group">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="group-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="group-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="css:colon"/> <!-- for maps -->
+      </include>
+    </context>
+
+
+    <!-- data types -->
+
+    <context id="scss-string-content">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="css:string-content" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:string-content" ref="scss-string-content"/>
+
+
+    <!-- functions -->
+
+    <context id="variable-arguments" style-ref="operator-symbol">
+      <match>\.\.\.</match>
+    </context>
+
+    <context id="scss-url">
+      <start>url\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:function"/>
+        <context sub-pattern="0" where="end" style-ref="css:function"/>
+        <!-- only accept multi-line comments because // is part of urls -->
+        <context ref="sass-c-like-comment-multiline"/>
+        <context ref="def:c-like-close-comment-outside-comment"/>
+        <context ref="css:string-value"/>
+      </include>
+    </context>
+
+    <context id="scss-function-content">
+      <include>
+        <context ref="css:function-content" original="true"/>
+        <context ref="variable-arguments"/>
+        <context ref="css:colon"/> <!-- for named arguments -->
+      </include>
+    </context>
+
+    <replace id="css:url" ref="scss-url"/>
+    <replace id="css:function-content" ref="scss-function-content"/>
+
+
+    <!-- data values -->
+
+    <context id="scss-name-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="interpolation"/> <!-- outputs unquoted strings -->
+        <context ref="variable"/>
+        <context ref="css:name-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-string-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="string-group"/>
+        <context ref="variable"/>
+        <context ref="css:string-value" original="true"/>
+        <context ref="string-operator"/>
+      </include>
+    </context>
+
+    <context id="scss-data-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="data-group"/>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="css:string-value" original="true"/>
+        <context ref="css:color-value"/>
+        <context ref="css:number-value"/>
+        <context ref="css:unicode-range"/>
+        <context ref="arithmetic-operator"/>
+      </include>
+    </context>
+
+    <replace id="css:name-value" ref="scss-name-value"/>
+    <replace id="css:string-value" ref="scss-string-value"/>
+    <replace id="css:data-value" ref="scss-data-value"/>
+
+
+    <!-- any assignable value -->
+
+    <context id="scss-any-value">
+      <include>
+        <context ref="css:function-call"/>
+        <context ref="any-group"/>
+        <context ref="parent-selector-list"/>
+        <context ref="bracketed-list"/>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="boolean"/>
+        <context ref="null"/>
+        <context ref="css:property-value-keyword"/>
+        <context ref="css:string-value" original="true"/>
+        <context ref="css:color-value"/>
+        <context ref="css:number-value"/>
+        <context ref="css:unicode-range"/>
+        <context ref="logical-operator"/>
+        <context ref="comparison-operator"/>
+        <context ref="arithmetic-operator"/>
+        <context ref="css:slash"/>
+        <context ref="css:comma"/>
+      </include>
+    </context>
+
+    <replace id="css:any-value" ref="scss-any-value"/>
+
+
+    <!-- Sass modifiers -->
+
+    <context id="variable-assignment-modifiers" style-ref="css:modifier">
+      <keyword>default</keyword>
+      <keyword>global</keyword>
+    </context>
+
+    <context id="at-extend-modifiers" style-ref="css:modifier">
+      <keyword>optional</keyword>
+    </context>
+
+
+    <!-- modifiers -->
+
+    <context id="scss-modifier-content">
+      <include>
+        <context ref="variable-assignment-modifiers"/>
+        <context ref="at-extend-modifiers"/>
+        <context ref="css:modifier-content" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:modifier-content" ref="scss-modifier-content"/>
+
+
+    <!-- style properties -->
+
+    <context id="scss-property-name">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="css:property-name" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:property-name" ref="scss-property-name"/>
+
+
+    <!-- style block -->
+
+    <context id="nested-properties" end-parent="true">
+      <start>\{</start>
+      <end>\}</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:block-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="css:block-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:declaration"/>
+      </include>
+    </context>
+
+    <context id="scss-declaration-property">
+      <include>
+        <context ref="variable"/> <!-- variable assignment -->
+        <context ref="css:declaration-property" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-declaration-value-content">
+      <include>
+        <context ref="nested-properties"/>
+        <context ref="css:declaration-value-content" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-declaration-value">
+      <start extended="true">
+        (?(DEFINE)
+          (?&lt;interpolation&gt;  # recursive subpattern to find matching brackets
+            \#\{
+              (?:
+                (?>
+                  (?:
+                    [^#{}]+ |
+                    (?! \#\{ | \} ) .
+                  )+
+                ) |
+                (?&amp;interpolation)
+              )*
+            \}
+          )
+        )
+        :
+        (?:
+          (?!                                       # not the start of a
+            \%{css:single-identifier-char-regex} |  #   pseudo-class
+            [:\\] |                                 #   pseudo-element, escape
+            \#\{                                    #   interpolation
+          ) |                                       # or
+          (?=                                       # ends like a normal declaration
+            (?&gt;
+              (?:
+                [^;}{#]+ |
+                (?&amp;interpolation)+ |
+                \#+
+              )*
+            )
+            \%{css:declaration-value-end}           #   with a semicolon or at the end of a block
+          )
+        )
+      </start>
+      <end>\%{css:declaration-value-end}</end> <!-- nested-properties has end-parent="true" -->
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="css:declaration-value-content"/>
+      </include>
+    </context>
+
+    <context id="scss-style-block-content">
+      <include>
+        <context ref="css:style-block-content" original="true"/>
+        <context ref="css:at-rule"/>
+        <context ref="css:selector"/>
+        <context ref="css:style-block"/>
+      </include>
+    </context>
+
+    <replace id="css:declaration-property" ref="scss-declaration-property"/>
+    <replace id="css:declaration-value-content" ref="scss-declaration-value-content"/>
+    <replace id="css:declaration-value" ref="scss-declaration-value"/>
+    <replace id="css:style-block-content" ref="scss-style-block-content"/>
+
+
+    <!-- media queries -->
+
+    <context id="scss-media-type-value">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="css:media-type-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-media-feature-test-name">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="css:media-feature-test-name" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-media-feature-test-value-content">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="css:media-feature-test-value-content" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:media-type-value" ref="scss-media-type-value"/>
+    <replace id="css:media-feature-test-name" ref="scss-media-feature-test-name"/>
+    <replace id="css:media-feature-test-value-content" ref="scss-media-feature-test-value-content"/>
+
+
+    <!-- Sass at-rules -->
+
+    <!--
+    @extend <selector> <optional modifier>?;
+    -->
+
+    <context id="at-extend">
+      <start>@extend\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:selector"/>
+        <context ref="css:modifier"/>
+        <context ref="css:at-rule-delimiter"/>
+      </include>
+    </context>
+
+    <!--
+    @at-root (<selector>|<query>)? <css-block>
+    -->
+
+    <context id="at-at-root-query-type" style-ref="css:property-name">
+      <keyword>without</keyword>
+      <keyword>with</keyword>
+    </context>
+
+    <context id="at-at-root-query-directive" style-ref="css:keyword">
+      <keyword>all</keyword>
+      <keyword>media</keyword>
+      <keyword>rule</keyword>
+      <keyword>supports</keyword>
+    </context>
+
+    <context id="at-at-root-query-value">
+      <start>:</start>
+      <end>\%{css:test-value-end}</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="at-at-root-query-directive"/>
+      </include>
+    </context>
+
+    <context id="at-at-root-query">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:test-delimiter"/>
+        <context sub-pattern="0" where="end" style-ref="css:test-delimiter"/>
+        <context ref="css:comment"/>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="at-at-root-query-type"/>
+        <context ref="at-at-root-query-value"/>
+      </include>
+    </context>
+
+    <context id="at-at-root">
+      <start>@at-root\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:selector"/>
+        <context ref="at-at-root-query"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @debug <any-value>;
+    @warn <any-value>;
+    @error <any-value>;
+    -->
+
+    <context id="at-debug-warn-error">
+      <start>@(debug|warn|error)\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="css:at-rule-delimiter"/>
+      </include>
+    </context>
+
+    <!--
+    @if <any-value> <css-block>
+    @else if <any-value> <css-block>
+    @else <css-block>
+    -->
+
+    <context id="at-if-else-if">
+      <start>@(if|else\s+if)\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <context id="at-else">
+      <start>@else\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @for <variable> from <any-value> (through|to) <any-value> <css-block>
+    -->
+
+    <context id="at-for-keyword" style-ref="css:at-rule-operator">
+      <keyword>from</keyword>
+      <keyword>through</keyword>
+      <keyword>to</keyword>
+    </context>
+
+    <context id="at-for">
+      <start>@for\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="at-for-keyword"/>
+        <context ref="variable"/>
+        <context ref="css:any-value"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @each <variable> in <list> <css-block>
+    -->
+
+    <context id="at-each-keyword" style-ref="css:at-rule-operator">
+      <keyword>in</keyword>
+    </context>
+
+    <context id="at-each">
+      <start>@each\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="at-each-keyword"/>
+        <context ref="variable"/>
+        <context ref="css:any-value"/>
+        <context ref="css:comma"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @while <expression> <css-block>
+    -->
+
+    <context id="at-while">
+      <start>@while\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @mixin <mixin name> <mixin-parameters>? <css-block>
+    -->
+
+    <context id="mixin-parameters">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="mixin-name"/>
+        <context sub-pattern="0" where="end" style-ref="mixin-name"/>
+        <context ref="css:comment"/>
+        <context ref="css:function-content"/>
+      </include>
+    </context>
+
+    <context id="at-mixin">
+      <start>@mixin\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:name" style-ref="mixin-name"/>
+        <context ref="mixin-parameters"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @include <mixin name> <mixin-parameters>? (;|<css-block>)
+    -->
+
+    <context id="at-include">
+      <start>@include\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:name" style-ref="mixin-name"/>
+        <context ref="mixin-parameters"/>
+        <context ref="css:at-rule-delimiter"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @content;
+    -->
+
+    <context id="at-content">
+      <start>@content\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:at-rule-delimiter"/>
+      </include>
+    </context>
+
+    <!--
+    @function <function name> <function-parameters> <css-block>
+    -->
+
+    <context id="function-parameters">
+      <start>\(</start>
+      <end>\)</end>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:function"/>
+        <context sub-pattern="0" where="end" style-ref="css:function"/>
+        <context ref="css:comment"/>
+        <context ref="css:function-content"/>
+      </include>
+    </context>
+
+    <context id="at-function">
+      <start>@function\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <!-- we define it this way because there can be whitespace
+             between the function name and the parentheses -->
+        <context ref="css:name" style-ref="css:function"/>
+        <context ref="function-parameters"/>
+        <context ref="css:at-rule-css-block"/>
+      </include>
+    </context>
+
+    <!--
+    @return <any-value>;
+    -->
+
+    <context id="at-return">
+      <start>@return\%]</start>
+      <include>
+        <context sub-pattern="0" where="start" style-ref="css:at-rule"/>
+        <context ref="css:comment"/>
+        <context ref="css:any-value"/>
+        <context ref="css:at-rule-delimiter"/>
+      </include>
+    </context>
+
+
+    <!-- at-rules -->
+
+    <context id="scss-font-feature-value-declaration-value-content">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="variable"/>
+        <context ref="css:font-feature-value-declaration-value-content" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-keyframe-selector-value">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="css:keyframe-selector-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-namespace-value">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="css:namespace-value" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-at-rule">
+      <include>
+
+        <context ref="at-extend"/>
+        <context ref="at-at-root"/>
+
+        <context ref="at-debug-warn-error"/>
+
+        <context ref="at-if-else-if"/>
+        <context ref="at-else"/>
+
+        <context ref="at-for"/>
+        <context ref="at-each"/>
+        <context ref="at-while"/>
+
+        <context ref="at-mixin"/>
+        <context ref="at-include"/>
+        <context ref="at-content"/>
+
+        <context ref="at-function"/>
+        <context ref="at-return"/>
+
+        <context ref="css:at-rule" original="true"/>
+
+      </include>
+    </context>
+
+    <replace id="css:at-rule-style-block-content" ref="scss-style-block-content"/>
+    <replace id="css:at-rule-css-block-content" ref="scss-style-block-content"/>
+    <replace id="css:font-feature-value-declaration-value-content" 
ref="scss-font-feature-value-declaration-value-content"/>
+    <replace id="css:keyframe-selector-value" ref="scss-keyframe-selector-value"/>
+    <replace id="css:namespace-value" ref="scss-namespace-value"/>
+    <replace id="css:at-rule" ref="scss-at-rule"/>
+
+
+    <!-- Sass selectors -->
+
+    <context id="parent-combinator">
+      <match>(&amp;)(\%{css:identifier-chars-regex}?)</match>
+      <include>
+        <context sub-pattern="1"  style-ref="css:combinator"/>
+        <context sub-pattern="2"  style-ref="selector-fragment"/>
+      </include>
+    </context>
+
+    <context id="placeholder-selector" style-ref="placeholder-selector">
+      <match>%\%{css:identifier-regex}</match>
+    </context>
+
+    <context id="interpolation-fragment" style-ref="selector-fragment">
+      <match>(?&lt;=\})\%{css:identifier-chars-regex}</match>
+    </context>
+
+
+    <!-- selectors -->
+
+    <context id="scss-simple-selector">
+      <include>
+        <context ref="interpolation"/> <!-- include in simple selector to be included in :not() -->
+        <context ref="interpolation-fragment"/>
+        <context ref="css:simple-selector" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-combinator">
+      <include>
+        <context ref="parent-combinator"/>
+        <context ref="css:combinator" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-nth-pseudo-class-argument-content">
+      <include>
+        <context ref="interpolation"/>
+        <context ref="css:nth-pseudo-class-argument-content" original="true"/>
+      </include>
+    </context>
+
+    <context id="scss-selector">
+      <include>
+        <context ref="placeholder-selector"/>
+        <context ref="css:selector" original="true"/>
+      </include>
+    </context>
+
+    <replace id="css:simple-selector" ref="scss-simple-selector"/>
+    <replace id="css:combinator" ref="scss-combinator"/>
+    <replace id="css:nth-pseudo-class-argument-content" ref="scss-nth-pseudo-class-argument-content"/>
+    <replace id="css:selector" ref="scss-selector"/>
+
+
+    <!-- top level declarations -->
+
+    <context id="top-level-declaration-property">
+      <include>
+        <context ref="variable"/>
+      </include>
+    </context>
+
+    <context id="top-level-declaration">
+      <include>
+        <context ref="top-level-declaration-property"/>
+        <context ref="css:declaration-value"/>
+        <context ref="css:modifier"/>
+        <context ref="css:semicolon"/>
+      </include>
+    </context>
+
+
+    <!-- main context -->
+
+    <context id="scss" class="no-spell-check">
+      <include>
+        <context ref="top-level-declaration"/>
+        <context ref="css:css"/>
+      </include>
+    </context>
+
+  </definitions>
+</language>
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 57e45314..e584dfdc 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -68,6 +68,7 @@ data/language-specs/json.lang
 data/language-specs/julia.lang
 data/language-specs/kotlin.lang
 data/language-specs/latex.lang
+data/language-specs/less.lang
 data/language-specs/lex.lang
 data/language-specs/libtool.lang
 data/language-specs/llvm.lang
@@ -113,6 +114,7 @@ data/language-specs/rust.lang
 data/language-specs/scala.lang
 data/language-specs/scheme.lang
 data/language-specs/scilab.lang
+data/language-specs/scss.lang
 data/language-specs/sh.lang
 data/language-specs/sml.lang
 data/language-specs/sparql.lang
diff --git a/tests/syntax-highlighting/file.less b/tests/syntax-highlighting/file.less
new file mode 100644
index 00000000..ef8308eb
--- /dev/null
+++ b/tests/syntax-highlighting/file.less
@@ -0,0 +1,779 @@
+/*
+ * nesting
+ */
+
+#header {
+    width: 300px;
+
+    .navigation {
+        font-size: 12px;
+    }
+
+    @media (min-width: 768px) {
+        width: 600px;
+
+        @media (min-resolution: 192dpi) {
+            background-image: url(/img/retina2x.png);
+        }
+    }
+}
+
+
+/*
+ * parent selector
+ */
+
+.component {
+    &:hover {
+        text-decoration: underline;
+    }
+
+    // compound selector
+    &__element,
+    &--modifier {
+        border: 1px;
+    }
+
+    // multiple &
+    & + & {
+        color: red;
+    }
+
+    // changing selector order
+    body.firefox & {
+        font-weight: normal;
+    }
+}
+
+
+/*
+ * variables, data types
+ */
+
+// assignment
+@number:                      5em;
+@double-quoted-string:        "foo";
+@single-quoted-string:        'bar';
+@double-quoted-escape-string: ~"foo";
+@single-quoted-escape-string: ~'bar';
+@not-quoted-string:           baz;
+@spaces-list:                 1px 2px 3px 4px;
+@commas-list:                 arial, some-other-arial, sans-serif;
+@color:                       #fe57a1;
+
+@detached-ruleset: {
+    color: red;
+
+    div {
+        color: blue;
+    }
+};
+
+#main {
+    // property value
+    width: @width;
+
+    // variable reference
+    color: @@color-variable-name;
+
+    // property variable
+    background-color: $color;
+
+    // use detached ruleset
+    @detached-ruleset(); // parentheses required
+}
+
+// media type, feature test
+@import "foo" @type and @feature-test, (@feature-test-content);
+@media @type and @feature-test, (@feature-test-content) {
+    width: 700px;
+}
+
+// font feature value
+@font-feature-values Font One {
+    @styleset {
+        nice-style: @value;
+    }
+}
+
+// @keyframes name
+@keyframes @myanim {
+    from { width: 0; }
+    to   { width: 100%; }
+}
+
+
+/*
+ * cases where Less doesn't currently accept variables (as of Less 3.0.0)
+ * but may be still be incidentally highlighted
+ */
+
+// font feature values font name
+@font-feature-values @fontname {
+    @styleset {
+        nice-style: 12;
+    }
+}
+
+// @namespace name
+@namespace @name url('http://www.w3.org/2000/svg');
+
+// @page custom name, pseudo-page
+@page @name {
+    margin: 0;
+
+    @left-top {
+       content: '';
+       }
+}
+
+
+/*
+ * variable interpolation
+ */
+
+// selector
+@{hash-id},
+#@{id},
+#i@{d},
+@{whole-class},
+.part-@{class}-fragment,
+@{div},
+d@{i}v,
+[@{attr}=@{value}],
+[@{attr-selector-content}],
+@{attr-selector},
+@{selection},
+::se@{lect}ion,
+@{hover},
+:ho@{v}er,
+@{lang-fr},
+:la@{ng}(fr),
+:lang(@{fr}),
+:nth-child(@{expr}),
+:not(@{div}) {
+
+    // property name
+    @{font}: serif;
+    background-@{image}: none;
+    border-@{bottom}-width: 0;
+
+    // inside strings
+    content: "@{var}";
+    content: '@{var}';
+
+    // nested interpolations
+    content: "@{@{nested}-variable}";
+}
+
+// font feature type, custom name
+@font-feature-values Font One {
+    @{type} {
+        @{name}: 12;
+    }
+}
+
+// inside @import url()
+@import url("http://fonts.googleapis.com/css?family=@{family}";);
+// inside @import string
+@import "http://fonts.googleapis.com/css?family=@{family}";;
+
+// @keyframes selectors
+@keyframes myanim {
+    @{from} { width: 0; }
+    @{to}   { width: 100%; }
+}
+
+// @page margin box type
+@page toc, :first {
+    margin: 0;
+
+    @{margin-box-type} {
+       content: '';
+       }
+}
+
+
+/*
+ * operations
+ */
+
+body {
+    width: (1px + (2em - 3rem)) * (4 / 5vh);
+
+    // unary negation
+    z-index: -@z-index;
+    margin-bottom: -(@base * 2);
+}
+
+
+/*
+ * functions
+ */
+
+body {
+    // Less string format function
+    content: %("1 plus 2 is %d", 1 + 2);
+
+    // single line comments not parsed inside url()
+    background: url(http://example.com/styles.css);
+
+    // semicolons separators
+    color: hsl(90; 100%; 50%);
+}
+
+
+/*
+ * extend
+ */
+
+// attached to selector
+#main:extend(.card-style),
+.sidebar :extend(div pre) {
+    color: blue;
+}
+
+#main {
+    // inside ruleset
+    &:extend(#board:nth-child(2n+3));
+
+    // all keyword
+    &:extend(div ~ .inline[name="foo"] all);
+}
+
+
+/*
+ * property merge
+ */
+
+code {
+    // with comma
+    box-shadow+: 0 0 20px black;
+
+    // with space
+    transform+_: scale(2);
+}
+
+
+/*
+ * mixins
+ */
+
+// mixin definition
+.black() {
+    color: black;
+}
+
+// with parameters
+.box(@margin, @padding) { // comma separated
+    margin: @margin;
+    padding: @padding;
+}
+.box(@margin; @padding) { // semicolon separated
+    margin: @margin;
+    padding: @padding;
+}
+.box(@margin: 10px; @padding: 10px) { // default parameters
+    margin: @margin;
+    padding: @padding;
+}
+
+// @arguments
+.box-shadow(@x: 0; @y: 0; @blur: 1px; @color: #000) {
+    box-shadow: @arguments;
+}
+
+// variable arguments
+.mixin(...) {} // 0 or more arguments
+.mixin(@a; ...) {} // at least 1 argument
+.mixin(@a; @rest...) {} // remaining arguments bound to variable
+
+// with pattern
+.mixin(dark; 5px) {}
+.mixin(light; #000) {}
+
+// mixin call
+#main {
+    // existing styles
+    #header;
+    .card-style;
+
+    // existing styles with parentheses (optional),
+    // or mixins with parentheses and no required parameters
+    #header();
+    .card-style();
+
+    // namespaced styles
+    #outer > .inner;
+    #outer .inner;
+    #outer.inner;
+
+    // important modifier
+    #header !important;
+    .card-style() !important;
+    #outer > .inner !important;
+
+    // with parameters
+    .box(20px, 10px); // comma separated
+    .box(20px; 10px); // semicolon separated
+    .box(@padding: 10px; @margin: 20px); // named parameters
+    .box(@list...); // expands values in @list as arguments
+    .mixin({ // detached ruleset
+        color: red;
+        div {
+            color: blue;
+        }
+    });
+
+    // with pattern
+    .mixin(dark; 5px);
+    .mixin(light; #000);
+}
+
+
+/*
+ * guards
+ */
+
+// regular styles
+button when (@my-option = true) {
+    color: white;
+}
+& when (@my-option = true) {
+    button {
+        color: white;
+    }
+    a {
+        color: blue;
+    }
+}
+
+// namespace
+#namespace when (@mode = huge) {
+    .mixin() {}
+}
+#namespace {
+    .mixin() when (@mode = huge) {}
+}
+
+// mixin
+.mixin (@a) when (lightness(@a) >= 50%) {
+    background-color: black;
+}
+.mixin (@a) when (lightness(@a) < 50%) {
+    background-color: white;
+}
+
+// comparison operators, logical operators
+.mixin (@a) when (@a >= 0) and (@a =< 0) {}
+.mixin (@a) when (@a < 0) or (@a > 0) {}
+.mixin (@a) when (@a < 0), (@a > 0) {}
+.mixin (@a) when not (@a = 0) {}
+
+// true keyword
+.mixin (@a) when (@a) {}
+.mixin (@a) when (@a = true) {}
+
+
+/*
+ * at-rules
+ */
+
+// @import
+@import "@{themes}/tidal-wave.less";
+@import (optional, reference) "foo.less" screen and (orientation: landscape), print; // import options
+
+// @plugin
+@plugin 'my-plugin';
+@plugin (options) 'my-plugin'; // plugin options
+
+// @plugin function call
+my-plugin();
+div {
+    my-plugin(red; 10px);
+}
+
+
+/*
+ * test cases
+ */
+
+.declarations-or-selectors {
+    // declarations
+    display:block;
+    font-family:arial;
+    @{property}:block;
+
+    // selectors
+    input:focus {
+        opacity: 0.5;
+    }
+    div:nth-child(2n+1) {
+       background-color: gray;
+    }
+    @{selector}:focus {
+        color: blue;
+    }
+    a:@{state} {
+        color: blue;
+    }
+}
+
+
+// from file.css
+
+/*
+ * general
+ */
+
+/* whitespace */
+#main
+{
+    color:aqua;
+    float: left!important;
+    margin  :  0  ;
+    width
+        :
+        100%
+        !
+        important
+        ;
+}
+
+/* case insensitivity */
+Body {
+    FONT: 12Px/16pX iTaLiC sans-SERIF;
+}
+
+
+/*
+ * selectors
+ */
+
+/* simple selectors */
+#testID,       /* id */
+.someclass,    /* class */
+div,           /* type */
+*,             /* universal */
+[lang|="zh"] { /* attribute */
+    color: black;
+}
+
+/* combinators */
+header + main, /* adjacent sibling */
+li ~ li,       /* general sibling */
+ul > li,       /* child */
+ul ul {        /* descendant */
+    color: blue;
+}
+
+/* pseudo-elements */
+:after,
+::after,
+::placeholder,
+::selection {
+    color: green;
+}
+
+/* pseudo-classes */
+:hover,
+:required,
+:lang(fr),
+:not(div#sidebar.fancy),
+:nth-child(n+1),
+:nth-last-child(-2n - 30),
+:nth-of-type(5),
+:nth-last-of-type(even) {
+    color: yellow;
+}
+
+/* pseudo-classes with invalid arguments */
+:not(div:before),         /* pseudo-element */
+:not(input::placeholder), /* pseudo-element */
+:not(p:not(:lang(en))),   /* nested :not */
+:nth-child(1.2n),         /* non-integer */
+:nth-child(.5),           /* non-integer */
+:nth-child(n+-1) {        /* number sign */
+    color: red;
+}
+
+/* namespace qualified */
+a,           /* type in default namespace */
+svg|a,       /* type in specified namespace */
+|a,          /* type in no namespace */
+*|a,         /* type in all namespaces (including no namespace) */
+svg|*,       /* universal */
+svg|[fill] { /* attribute */
+    color: white;
+}
+
+
+/*
+ * basic data types
+ */
+
+#main {
+    /* angle */
+    transform: rotate(+33.333e+3deg);
+
+    /* color */
+    color: #f00;
+    color: #f00f; /* #rgba */
+    color: #ff0000;
+    color: #ff0000ff; /* #rrggbbaa */
+    color: red;
+    color: lightgoldenrodyellow;
+    color: rebeccapurple;
+    color: currentColor;
+
+    /* freqency (not currently used for any property) */
+    content: 44.1kHz;
+
+    /* integer */
+    z-index: +255;
+    z-index: -1;
+
+    /* length */
+    width: 10px;
+    width: 10.5rem;
+    width: -10e-2vw;
+
+    /* number */
+    opacity: .5;
+    opacity: 0.3333;
+    opacity: 1;
+    opacity: 2e-34;
+
+    /* percentage */
+    width: 100%;
+
+    /* string */
+    content: "double quoted";
+    content: 'single quoted';
+
+    /* time */
+    transition-duration: .4s;
+    transition-duration: 400ms;
+
+    /* unicode range */
+    unicode-range: U+0025-00FF;
+    unicode-range: U+4??; /* wildcard range */
+}
+
+/* ratio */
+@media (min-aspect-ratio: 16/9) {}
+
+/* resolution */
+@media (min-resolution: +2.54dpcm) {}
+
+
+/*
+ * identifiers
+ */
+
+/* leading hyphens */
+#-here.-there,
+#-- .--everywhere { /* two hyphens: https://stackoverflow.com/a/30822662 */
+    color: blue;
+}
+
+/* non-ASCII */
+#español,
+#你好,
+.❤♫ {
+    color: green;
+}
+
+/* invalid identifiers */
+#1id,      /* starts with digit */
+.-2class { /* starts with hyphen digit */
+    color: maroon;
+}
+
+
+/*
+ * escaping
+ */
+
+/* selectors */
+#\..\+\ space\@\>,                            /* special character escape */
+#\E9 dition .\0000E9dition .motion_\e9motion, /* Unicode character escape */
+.\e33 div,                                    /* trailing space terminates Unicode character escape */
+.\e33  div,                                   /* extra space to denote separate tokens */
+.\31 23 {                                     /* escape leading digit of identifier */
+
+    /* property value */
+    content: "\E9 dition \
+              \"\0000E9dition\" \
+              \e9motion";
+
+    /* function name */
+    background: \u\72\l(image.png);
+}
+
+
+/*
+ * functions
+ */
+
+#main {
+    /* url */
+    background: url("image.svg");
+
+    /* function argument keywords */
+    background-image: linear-gradient(to left top, #fff, blue);
+    grid-template-columns: repeat(2, minmax(max-content, 300px) 1fr) 100px;
+}
+
+
+/*
+ * style properties
+ */
+
+#main {
+    /* svg */
+    fill: url(#pattern);
+    text-rendering: optimizeLegibility;
+
+    /* css3 */
+    font-variant-east-asian: jis04;
+    size: letter;
+    transition-timing-function: ease-in;
+
+    /* animatable */
+    transition-property: height, font-size, visibility;
+}
+
+/*
+ * modifiers
+ */
+body {
+    background: pink !important;
+}
+
+
+/*
+ * media queries
+ */
+
+@media screen, (orientation: portrait) {}
+@media not (print and (min-monochrome: 16) and (color)) {}
+@media only screen {} @media not print {}
+
+
+/*
+ * at-rules
+ */
+
+/* @font-face */
+@font-face {
+    font-family: MyHelvetica;
+    src: local("Helvetica Neue"),
+         local("HelveticaNeue"),
+         url(MgOpenModerna.ttf);
+}
+
+/* @font-feature-values */
+@font-feature-values Font One {
+    @styleset {
+        nice-style: 12;
+    }
+}
+.nice-look {
+    font-variant-alternates: styleset(nice-style);
+}
+
+/* @import */
+@import URL("fineprint.css");
+@import 'custom.css';
+@import url('landscape.css') screen and (orientation: landscape), print;
+
+/* @keyframes */
+@keyframes myanim {
+    from { opacity: 0.0; }
+    50%  { opacity: 0.5; }
+    to   { opacity: 1.0; }
+}
+
+/* @media */
+@media all {
+    body {
+        background: gray;
+    }
+    @media screen, (orientation: portrait) {
+        body {
+            background: grey;
+        }
+    }
+}
+
+/* @namespace */
+@namespace "http://www.w3.org/1999/xhtml";;
+@namespace svg url(http://www.w3.org/2000/svg);
+
+/* @page */
+@page {
+    bleed: 1cm;
+}
+@page toc, :blank {
+    margin: 2cm;
+    marks: crop cross;
+}
+@page index:left {
+    size: A4;
+
+    @top-right {
+        content: "Page " counter(page);
+    }
+}
+
+/* @supports */
+@supports (animation-name: test) {
+    @keyframes 'my-complicated-animation' {
+        0%   { width: 0; }
+        100% { width: 100%; }
+    }
+}
+
+
+/*
+ * vendor-specific extensions
+ */
+
+/* pseudo-elements */
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+}
+input[type="number"] {
+    -moz-appearance: textfield;
+}
+
+/* pseudo-classes */
+#page:-moz-full-screen,
+#page:-ms-fullscreen,
+#page:-webkit-full-screen {
+    background: silver;
+}
+
+/* functions */
+.footer {
+    background-image: -webkit-linear-gradient(to left top, #fff, blue);
+}
+
+/* style properties */
+#sidebar {
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+}
+@supports not ((text-align-last: justify) or (-moz-text-align-last: justify)) {
+    body {
+        text-align: justify;
+    }
+}
+
+/* at-rules */
+@-webkit-keyframes myanim {
+    from { opacity: 0.0; }
+    50%  { opacity: 0.5; }
+    to   { opacity: 1.0; }
+}
diff --git a/tests/syntax-highlighting/file.scss b/tests/syntax-highlighting/file.scss
new file mode 100644
index 00000000..bc3dd5ef
--- /dev/null
+++ b/tests/syntax-highlighting/file.scss
@@ -0,0 +1,805 @@
+/*
+ * css extensions
+ */
+
+div {
+    color: #00ff00;
+
+    // nested rules
+    #inner.element {
+        color: #000000;
+    }
+
+    // parent selector
+    &:hover {
+        text-decoration: underline;
+    }
+    body.firefox & {
+        font-weight: normal;
+    }
+    &__element, // compound selector
+    &--modifier {
+        border: 1px;
+    }
+
+    // nested properties
+    font: {
+        family: fantasy;
+        size: 30em;
+        weight: bold;
+    }
+    font: 20px/24px fantasy {
+        weight: bold;
+    }
+}
+
+
+/*
+ * variables, data types
+ */
+
+// assignment
+$number:                5em;
+$double-quoted-string:  "foo";
+$single-quoted-string:  'bar';
+$not-quoted-string:     baz;
+$true:                  true;
+$false:                 false;
+$null:                  null;
+$parens-spaces-list:    (left top);
+$parens-commas-list:    (1px, 2px, 3px, 4px);
+$no-parens-spaces-list: 1px 2px 3px 4px;
+$no-parens-commas-list: arial, some-other-arial, sans-serif;
+$trailing-comma:        (1 2 3,); // a comma-separated list containing a space-separated list
+$bracketed-list:        [5rem 6rem 7rem]; // comma or space separated
+$map:                   (medium: 640, 'large': 960, "x-large": 1280, (xx-large,): 1600); // must have 
parentheses, be comma separated
+$color:                 #fe57a1;
+$function-reference:    get-function($function-name);
+
+// assign if not already assigned to
+$var: 1 !default;
+
+#main {
+    // global variable defined in block
+    $width: 5em !global;
+
+    // list of parent selectors
+    $selectors: &;
+
+    // property value
+    width: $width;
+}
+
+// media feature test
+@import "foo" ($orientation: $landscape);
+@media ($orientation-landscape) {
+    width: 700px;
+}
+
+// font feature value
+@font-feature-values Font One {
+    @styleset {
+        nice-style: $value;
+    }
+}
+
+// @supports test
+@supports ($animation-name: $test) {
+    body {
+        animation-name: test;
+    }
+}
+@supports ($animation-name-test) {
+    body {
+        animation-name: test;
+    }
+}
+
+// @at-root query
+@at-root ($type: $value) {
+    .top-level {
+        background: pink;
+    }
+}
+@at-root ($query) {
+    .top-level {
+        background: pink;
+    }
+}
+
+
+/*
+ * operations
+ */
+
+body {
+    // arithmetic operators
+    width: (1px + (2em - 3rem)) * (4 / 5vh) % 6cm;
+
+    // plain css
+    font: 10px/8px;
+    font: (italic bold 10px/8px);
+    font: #{$font-size}/#{$line-height};
+
+    // division
+    width: $width/2;
+    width: round(1.5)/2;
+    width: (500px/2);
+    width: 5px + 8px/2px;
+
+    // minus sign
+    animation-name: a-1; // identifier
+    margin: (5px - 3px) 5px-3px 3-2 (1 -$var); // subtraction
+    margin: 1 -3em; // negative number
+    margin: -$var -(1); // unary negation
+
+    // string concatenation
+    content: "Foo " + Bar; // "Foo Bar"
+    font-family: sans- + "serif"; // sans-serif
+}
+
+// string concatenation in string-only context
+@keyframes ('foo' + bar) {}
+@keyframes (foo + "bar") {}
+
+// comparison operators, logical operators
+$a: (1 < 2 and 1 > 2) or (1 <= 2) and 1 >= 2;
+$b: not (1 == 2) and 1 != 2;
+
+
+/*
+ * interpolation
+ */
+
+// selector
+#{'#id'},
+#i#{'d'},
+#{'.whole-class'},
+.part-#{'class'}-fragment,
+#{'div'},
+d#{'i'}v,
+[#{'attr'}=#{'value'}],
+[#{'attr="value"'}],
+#{'[attr="value"]'},
+#{'::selection'},
+::se#{'lect'}ion,
+#{':hover'},
+:ho#{'v'}er,
+#{':lang(fr)'},
+:la#{'ng(f'}r),
+:lang(#{'fr'}),
+:nth-child(#{'2n+1'}),
+:not(#{'div'}),
+#{'%placeholder'},
+%pla#{'ceho'}lder {
+
+    // property name
+    #{'font'}: serif;
+    background-#{'image'}: none;
+    border-#{'bottom'}-width: 0;
+
+    // property value
+    font-size: #{$font-size};
+    font-family: #{'arial, sans'}-serif;
+    width: #{5 * (3 - 1)}px;
+
+    // function name
+    background: #{'url'}('image.png');
+    background: u#{'r'}l('image.png');
+
+    // !important (exclamation mark needs to be inside)
+    width: 0 #{'!important'};
+    width: 0 #{'!import'}ant;
+
+    // inside strings
+    content: "#{$var}";
+    content: '#{$var}';
+
+    // inside comments
+    /* multi-line: #{$yes} */
+    // single line: #{$no}
+}
+
+// media type, feature test
+@import "foo" #{'screen'} and (#{'orientation'}: #{'landscape'});
+@media #{'screen'} and (#{'orientation: landscape'}) {
+    width: 700px;
+}
+
+// font name, font feature custom name, font feature value
+@font-feature-values #{'Font One'} {
+    @styleset { // interpolation not accepted here?
+        #{'nice-style'}: #{12};
+    }
+}
+
+// inside @import url()
+@import url("http://fonts.googleapis.com/css?family=#{$family}";);
+
+// @keyframes name, selector
+@keyframes #{'myanim'} {
+    #{'from'} { width: 0; }
+    #{'to'}   { width: 100%; }
+}
+
+// @namespace name
+@namespace #{'svg'} url('http://www.w3.org/2000/svg');
+
+// @page custom name, pseudo-page
+@page #{'toc'}, #{':first'} {
+    margin: 0;
+
+    @left-top { // interpolation not accepted here?
+        content: '';
+    }
+}
+
+// @supports test
+@supports (#{'animation-name'}: #{'test'}) {
+    body {
+        animation-name: test;
+    }
+}
+@supports (#{'animation-name: test'}) {
+    body {
+        animation-name: test;
+    }
+}
+
+// @at-root query
+@at-root (#{'without'}: #{'media'}) {
+    .top-level {
+        background: pink;
+    }
+}
+@at-root (#{'without: media'}) {
+    .top-level {
+        background: pink;
+    }
+}
+
+
+/*
+ * functions
+ */
+
+body {
+    // single line comments not parsed inside url()
+    background: url(http://example.com/styles.css);
+
+    // keyword arguments
+    color: hsl($hue: 0, $saturation: 100%, $lightness: 50%);
+}
+
+
+/*
+ * at-rules
+ */
+
+// @import
+@import "rounded-corners", "text-shadow"; // multiple files
+#main { // nested @import
+    @import "example"; // but not inside mixins or control directives
+}
+
+// nested @media
+@media screen {
+    .sidebar {
+        @media (orientation: landscape) {
+            width: 500px;
+        }
+    }
+}
+
+// @extend
+#main {
+    @extend #hello;
+
+    // no error if not found
+    @extend .from[the="other-side"] !optional;
+
+    // placeholder selector
+    @extend div%placeholder;
+}
+
+// @at-root
+#main {
+    @at-root .child {
+        color: red;
+    }
+}
+@media screen {
+    @supports (font-variant-alternates: styleset(nice-style)) {
+        @at-root (without: media supports) { // with query
+            .absolutely-top-level {
+                background: pink;
+            }
+        }
+    }
+}
+
+// @if/@else if/@else, @debug/@warn/@error
+@if $num-errors == 0 {
+    @debug "$num-errors is 0";
+} @else if $num-errors > 0 {
+    @warn "oops there are #{$num-errors} errors";
+} @else {
+    @error "negative errors?!?";
+}
+
+// @for
+@for $i from 1 through 3 {
+    .item-#{$i} {
+        width: 2em * $i;
+    }
+}
+
+// @each
+@each $animal in puma, sea-slug, egret, salamander {
+    .#{$animal}-icon {
+        background-image: url('/images/#{$animal}.png');
+    }
+}
+@each $header, $size in (h1: 2em, h2: 1.5em, h3: 1.2em) {
+    #{$header} {
+        font-size: $size;
+    }
+}
+
+// @while
+$i: 6;
+@while $i > 0 {
+    .item-#{$i} {
+        width: 2em * $i;
+    }
+    $i: $i - 2;
+}
+
+// @mixin
+@mixin large-text {
+    font-size: 128px;
+}
+@mixin sexy-border($color, $width: 1in) { // with arguments, default value
+    border: {
+        color: $color;
+        width: $width;
+    }
+}
+@mixin box-shadow($shadows...) { // variable arguments
+    box-shadow: $shadows;
+}
+@mixin apply-to-ie6-only { // accepts content block
+    * html {
+        @content;
+    }
+}
+
+// @include
+.page-title {
+    @include large-text;
+}
+p {
+    @include sexy-border(blue); // with arguments
+}
+div {
+    @include sexy-border($color: blue, $width: 10cm); // keyword arguments
+}
+.primary {
+    @include box-shadow($shadows...); // expand list into arguments
+}
+@include apply-to-ie6-only { // passing content block
+    display: block;
+
+    #main {
+        background: black;
+    }
+}
+
+// @function
+@function grid-width($n) {
+    @return $n * $grid-width + ($n - 1) * $gutter-width;
+}
+#sidebar {
+    width: grid-width(5);
+}
+
+
+/*
+ * test cases
+ */
+
+.declarations-or-selectors {
+    // declarations
+    display:block;
+    font-family:arial;
+    font: {
+        family: fantasy;
+        weight: bold;
+    }
+    font: fantasy {
+        size: 20px;
+        style: italic;
+    }
+    #{$property}:block;
+    #{$property}: {
+        color: red;
+    }
+    color:#000;
+    width:#{$width};
+
+    // selectors
+    input:focus {
+        opacity: 0.5;
+    }
+    div:nth-child(2n+1) {
+       background-color: gray;
+    }
+    div:-moz-full-screen {
+        display: block;
+    }
+    a:#{$state} {
+        color: blue;
+    }
+    #{$selector}:focus {
+        color: blue;
+    }
+}
+
+
+// from file.css
+
+/*
+ * general
+ */
+
+/* whitespace */
+#main
+{
+    color:aqua;
+    float: left!important;
+    margin  :  0  ;
+    width
+        :
+        100%
+        !
+        important
+        ;
+}
+
+/* case insensitivity */
+Body {
+    FONT: 12Px/16pX iTaLiC sans-SERIF;
+}
+
+
+/*
+ * selectors
+ */
+
+/* simple selectors */
+#testID,       /* id */
+.someclass,    /* class */
+div,           /* type */
+*,             /* universal */
+[lang|="zh"] { /* attribute */
+    color: black;
+}
+
+/* combinators */
+header + main, /* adjacent sibling */
+li ~ li,       /* general sibling */
+ul > li,       /* child */
+ul ul {        /* descendant */
+    color: blue;
+}
+
+/* pseudo-elements */
+:after,
+::after,
+::placeholder,
+::selection {
+    color: green;
+}
+
+/* pseudo-classes */
+:hover,
+:required,
+:lang(fr),
+:not(div#sidebar.fancy),
+:nth-child(n+1),
+:nth-last-child(-2n - 30),
+:nth-of-type(5),
+:nth-last-of-type(even) {
+    color: yellow;
+}
+
+/* pseudo-classes with invalid arguments */
+:not(div:before),         /* pseudo-element */
+:not(input::placeholder), /* pseudo-element */
+:not(p:not(:lang(en))),   /* nested :not */
+:nth-child(1.2n),         /* non-integer */
+:nth-child(.5),           /* non-integer */
+:nth-child(n+-1) {        /* number sign */
+    color: red;
+}
+
+/* namespace qualified */
+a,           /* type in default namespace */
+svg|a,       /* type in specified namespace */
+|a,          /* type in no namespace */
+*|a,         /* type in all namespaces (including no namespace) */
+svg|*,       /* universal */
+svg|[fill] { /* attribute */
+    color: white;
+}
+
+
+/*
+ * basic data types
+ */
+
+#main {
+    /* angle */
+    transform: rotate(+33.333e+3deg);
+
+    /* color */
+    color: #f00;
+    color: #f00f; /* #rgba */
+    color: #ff0000;
+    color: #ff0000ff; /* #rrggbbaa */
+    color: red;
+    color: lightgoldenrodyellow;
+    color: rebeccapurple;
+    color: currentColor;
+
+    /* freqency (not currently used for any property) */
+    content: 44.1kHz;
+
+    /* integer */
+    z-index: +255;
+    z-index: -1;
+
+    /* length */
+    width: 10px;
+    width: 10.5rem;
+    width: -10e-2vw;
+
+    /* number */
+    opacity: .5;
+    opacity: 0.3333;
+    opacity: 1;
+    opacity: 2e-34;
+
+    /* percentage */
+    width: 100%;
+
+    /* string */
+    content: "double quoted";
+    content: 'single quoted';
+
+    /* time */
+    transition-duration: .4s;
+    transition-duration: 400ms;
+
+    /* unicode range */
+    unicode-range: U+0025-00FF;
+    unicode-range: U+4??; /* wildcard range */
+}
+
+/* ratio */
+@media (min-aspect-ratio: 16/9) {}
+
+/* resolution */
+@media (min-resolution: +2.54dpcm) {}
+
+
+/*
+ * identifiers
+ */
+
+/* leading hyphens */
+#-here.-there,
+#-- .--everywhere { /* two hyphens: https://stackoverflow.com/a/30822662 */
+    color: blue;
+}
+
+/* non-ASCII */
+#español,
+#你好,
+.❤♫ {
+    color: green;
+}
+
+/* invalid identifiers */
+#1id,      /* starts with digit */
+.-2class { /* starts with hyphen digit */
+    color: maroon;
+}
+
+
+/*
+ * escaping
+ */
+
+/* selectors */
+#\..\+\ space\@\>,                            /* special character escape */
+#\E9 dition .\0000E9dition .motion_\e9motion, /* Unicode character escape */
+.\e33 div,                                    /* trailing space terminates Unicode character escape */
+.\e33  div,                                   /* extra space to denote separate tokens */
+.\31 23 {                                     /* escape leading digit of identifier */
+
+    /* property value */
+    content: "\E9 dition \
+              \"\0000E9dition\" \
+              \e9motion";
+
+    /* function name */
+    background: \u\72\l(image.png);
+}
+
+
+/*
+ * functions
+ */
+
+#main {
+    /* url */
+    background: url("image.svg");
+
+    /* function argument keywords */
+    background-image: linear-gradient(to left top, #fff, blue);
+    grid-template-columns: repeat(2, minmax(max-content, 300px) 1fr) 100px;
+}
+
+
+/*
+ * style properties
+ */
+
+#main {
+    /* svg */
+    fill: url(#pattern);
+    text-rendering: optimizeLegibility;
+
+    /* css3 */
+    font-variant-east-asian: jis04;
+    size: letter;
+    transition-timing-function: ease-in;
+
+    /* animatable */
+    transition-property: height, font-size, visibility;
+}
+
+/*
+ * modifiers
+ */
+body {
+    background: pink !important;
+}
+
+
+/*
+ * media queries
+ */
+
+@media screen, (orientation: portrait) {}
+@media not (print and (min-monochrome: 16) and (color)) {}
+@media only screen {} @media not print {}
+
+
+/*
+ * at-rules
+ */
+
+/* @font-face */
+@font-face {
+    font-family: MyHelvetica;
+    src: local("Helvetica Neue"),
+         local("HelveticaNeue"),
+         url(MgOpenModerna.ttf);
+}
+
+/* @font-feature-values */
+@font-feature-values Font One {
+    @styleset {
+        nice-style: 12;
+    }
+}
+.nice-look {
+    font-variant-alternates: styleset(nice-style);
+}
+
+/* @import */
+@import URL("fineprint.css");
+@import 'custom.css';
+@import url('landscape.css') screen and (orientation: landscape), print;
+
+/* @keyframes */
+@keyframes myanim {
+    from { opacity: 0.0; }
+    50%  { opacity: 0.5; }
+    to   { opacity: 1.0; }
+}
+
+/* @media */
+@media all {
+    body {
+        background: gray;
+    }
+    @media screen, (orientation: portrait) {
+        body {
+            background: grey;
+        }
+    }
+}
+
+/* @namespace */
+@namespace "http://www.w3.org/1999/xhtml";;
+@namespace svg url(http://www.w3.org/2000/svg);
+
+/* @page */
+@page {
+    bleed: 1cm;
+}
+@page toc, :blank {
+    margin: 2cm;
+    marks: crop cross;
+}
+@page index:left {
+    size: A4;
+
+    @top-right {
+        content: "Page " counter(page);
+    }
+}
+
+/* @supports */
+@supports (animation-name: test) {
+    @keyframes 'my-complicated-animation' {
+        0%   { width: 0; }
+        100% { width: 100%; }
+    }
+}
+
+
+/*
+ * vendor-specific extensions
+ */
+
+/* pseudo-elements */
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+    -webkit-appearance: none;
+}
+input[type="number"] {
+    -moz-appearance: textfield;
+}
+
+/* pseudo-classes */
+#page:-moz-full-screen,
+#page:-ms-fullscreen,
+#page:-webkit-full-screen {
+    background: silver;
+}
+
+/* functions */
+.footer {
+    background-image: -webkit-linear-gradient(to left top, #fff, blue);
+}
+
+/* style properties */
+#sidebar {
+    -ms-overflow-style: -ms-autohiding-scrollbar;
+}
+@supports not ((text-align-last: justify) or (-moz-text-align-last: justify)) {
+    body {
+        text-align: justify;
+    }
+}
+
+/* at-rules */
+@-webkit-keyframes myanim {
+    from { opacity: 0.0; }
+    50%  { opacity: 0.5; }
+    to   { opacity: 1.0; }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]