diff --git a/.dev-assets/docs/TODO.md b/.dev-assets/docs/TODO.md new file mode 100644 index 0000000..69995ce --- /dev/null +++ b/.dev-assets/docs/TODO.md @@ -0,0 +1,15 @@ +# TO DO + +[ ] Add `light-dark` to `#function-colors` +[ ] Add scope for slash in `grid-column: 1/-1;` + +[ ] Check for and add newer selector keywords +[ ] Check for and add newer property keywords +[ ] Add newer @media property names and values + +[ ] Add 'at-rule-container': [Ref](https://developer.mozilla.org/en-US/docs/Web/CSS/@container) +[ ] Add 'at-rule-layer': [Ref](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) +[ ] Add 'at-rule-font-palette-values': [Ref](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-palette-values) +[ ] Add 'at-rule-property': [Ref](https://developer.mozilla.org/en-US/docs/Web/CSS/@property) + +[ ] Fix @supports so non-nested properties are not supported diff --git a/.dev-assets/docs/selector-rules.md b/.dev-assets/docs/selector-rules.md new file mode 100644 index 0000000..6fadaca --- /dev/null +++ b/.dev-assets/docs/selector-rules.md @@ -0,0 +1,98 @@ +# Rules of Selectors + + +## Shared keywords for pseudo selectors and property value +- active +- paused +- open +- disabled +- default +- optional + +## May Include + +- & (nesting selector) +- selector[] (attribute selector) +- .selector (class selector) +- #selector (id selector) +- keyword (type selector) ... can be namespaced ns|h1 or |h1 ... aka elemental selector or tag name +- - (universal selector) + +- selector.class +- selector#id + +## Patterns + +- - +- E +- E:(not|is|where|has)() +- E.class +- E#id +- E[attr] +- E:dir() - Directional pseudo-class +- E:lang() - Language pseudo-class +- E:(any-link|link|visited|local-link|target|target-within|scope) - Location pseudo-class +- E:(active|hover|focus|focus-within|focus-visible) - User Action Pseudo-classes +- E:(current|current()|past|future) - Time-dimensional Pseudo-classes + +### Resource State Pseudo-classes + +- E:(playing|paused|seeking) - Media Playback State +- E:(buffering|stalled) - Media Loading State +- E:(muted|volume-locked) - Sound States + +- E:(open|popover-open|modal|fullscreen|picture-in-picture) - Element Display State Pseudo-classes + +- E:(enabled|disabled|read-write|read-only|placeholder-shown|autofill|default|checked|indeterminate|valid|invalid|in-range|out-of-range|required|optional|blank|user-valid|user-invalid) + +- E:(root|empty|nth-child()|nth-last-child()|first-child|last-child|only-child|nth-of-type()|nth-last-of-type()|first-of-type|last-of-type|only-of-type) - Tree-Structural pseudo-classes + +### Grid-Structural Selectors + +- E F | E > F | E + F | E ~ F ... combinator (>|+|~|\\s) +- F || E | E:(nth-col()|nth-last-col()) - Grid-Structural Selectors + +## Complex Selectors + +selector.class +selector#id +selector{combinator} +selector:pseudo-class +selector::pseudo-element + +## 5 Aspects + +1. tag name +2. namespace +3. ID +4. Class +5. Attribute (name-value pairs) + +### May start or end with + +\*|\\||\\#|\\.|combinator|,|& + +### May start with + +### May end with + +:[\\w]+ | ::[\\w]+ | [] + +May NOT start with +digit + +May NOT end with +:\\s+ + +- Universal selector \* +- Nesting selector & +- Namespace selector | +- Class selector . +- Id selector # +- Whitespace \\s +- Combinator ~|+|> +- Comma , +- Closing bracket }|)|] +- Semicolon ; +- colon without whitespace :[^\\s] +- comment block \*/ diff --git a/.dev-assets/examples/extension-original-css.cson b/.dev-assets/examples/extension-original-css.cson new file mode 100644 index 0000000..13cfb1e --- /dev/null +++ b/.dev-assets/examples/extension-original-css.cson @@ -0,0 +1,2465 @@ +information_for_contributors: [ + 'This file has been converted from https://github.com/microsoft/vscode-css/blob/master/grammars/css.cson' + 'If you want to provide a fix or improvement, please create a pull request against the original repository.' + 'Once accepted there, we are happy to receive an update request.' +] +version: 'https://github.com/microsoft/vscode-css/commit/c216f777497265700ff336f739328e5197e012cd' +name: 'CSS' +scopeName: 'source.css' +patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#combinators' + } + { + include: '#selector' + } + { + include: '#at-rules' + } + { + include: '#rule-list' + } + { + include: '#nesting-selector' + } +] +repository: + 'arithmetic-operators': + match: '[*/]|(?<=\\s|^)[-+](?=\\s|$)' + name: 'keyword.operator.arithmetic.css' + 'at-rules': + patterns: [ + { + begin: '(?i)(?=@container(\\s|\\(|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)container' + beginCaptures: + '0': + name: 'keyword.control.at-rule.container.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*[{;])' + name: 'meta.at-rule.container.header.css' + patterns: [ + { + include: '#container-condition' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.container.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.container.end.bracket.curly.css' + name: 'meta.at-rule.container.body.css' + patterns: [ + { + include: '#nesting-at-rules' + } + { + include: '$self' + } + ] + } + ] + } + { + begin: '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' + end: ';|(?=$)' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.charset.css' + patterns: [ + { + captures: + '1': + name: 'invalid.illegal.not-lowercase.charset.css' + '2': + name: 'invalid.illegal.leading-whitespace.charset.css' + '3': + name: 'invalid.illegal.no-whitespace.charset.css' + '4': + name: 'invalid.illegal.whitespace.charset.css' + '5': + name: 'invalid.illegal.not-double-quoted.charset.css' + '6': + name: 'invalid.illegal.unclosed-string.charset.css' + '7': + name: 'invalid.illegal.unexpected-characters.charset.css' + match: ''' + (?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + captures: + '1': + name: 'keyword.control.at-rule.charset.css' + '2': + name: 'punctuation.definition.keyword.css' + match: '((@)charset)(?=\\s)' + } + { + begin: '"' + beginCaptures: + '0': + name: 'punctuation.definition.string.begin.css' + end: '"|$' + endCaptures: + '0': + name: 'punctuation.definition.string.end.css' + name: 'string.quoted.double.css' + patterns: [ + { + begin: '(?:\\G|^)(?=(?:[^"])+$)' + end: '$' + name: 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + } + { + begin: '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + beginCaptures: + '1': + name: 'keyword.control.at-rule.import.css' + '2': + name: 'punctuation.definition.keyword.css' + end: ';' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.import.css' + patterns: [ + { + begin: '\\G\\s*(?=/\\*)' + end: '(?<=\\*/)\\s*' + patterns: [ + { + include: '#comment-block' + } + ] + } + { + include: '#string' + } + { + include: '#url' + } + { + include: '#media-query-list' + } + ] + } + { + begin: '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + beginCaptures: + '1': + name: 'keyword.control.at-rule.font-face.css' + '2': + name: 'punctuation.definition.keyword.css' + end: '(?!\\G)' + name: 'meta.at-rule.font-face.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#rule-list' + } + ] + } + { + begin: '(?i)(@)page(?=[\\s:{]|/\\*|$)' + captures: + '0': + name: 'keyword.control.at-rule.page.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*($|[:{;]))' + name: 'meta.at-rule.page.css' + patterns: [ + { + include: '#rule-list' + } + ] + } + { + begin: '(?i)(?=@media(\\s|\\(|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)media' + beginCaptures: + '0': + name: 'keyword.control.at-rule.media.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*[{;])' + name: 'meta.at-rule.media.header.css' + patterns: [ + { + include: '#media-query-list' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.media.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.media.end.bracket.curly.css' + name: 'meta.at-rule.media.body.css' + patterns: [ + { + include: '#nesting-at-rules' + } + { + include: '$self' + } + ] + } + ] + } + { + begin: '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)counter-style' + beginCaptures: + '0': + name: 'keyword.control.at-rule.counter-style.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*{)' + name: 'meta.at-rule.counter-style.header.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + captures: + '0': + patterns: [ + { + include: '#escapes' + } + ] + match: ''' + (?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + name: 'variable.parameter.style-name.css' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.property-list.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.property-list.end.bracket.curly.css' + name: 'meta.at-rule.counter-style.body.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#rule-list-innards' + } + ] + } + ] + } + { + begin: '(?i)(?=@document([\\s\'"{;]|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)document' + beginCaptures: + '0': + name: 'keyword.control.at-rule.document.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*[{;])' + name: 'meta.at-rule.document.header.css' + patterns: [ + { + begin: '(?i)(?>>' + name: 'invalid.deprecated.combinator.css' + } + { + match: '>>|>|\\+|~' + name: 'keyword.operator.combinator.css' + } + ] + commas: + match: ',' + name: 'punctuation.separator.list.comma.css' + 'comment-block': + begin: '/\\*' + beginCaptures: + '0': + name: 'punctuation.definition.comment.begin.css' + end: '\\*/' + endCaptures: + '0': + name: 'punctuation.definition.comment.end.css' + name: 'comment.block.css' + 'container-condition': + begin: '\\G' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + match: ''' + (?xi) + (?<=not.*)not + # Only one `not` in allowed + ''' + name: 'invalid.illegal.multiple-not.container.css' + } + { + match: '(?i)(?<=\\s|^|,|\\*/)(and|or|not)(?=\\s|{|/\\*|$)' + name: 'keyword.operator.logical.$1.container.css' + } + { + include: '#container-name' + } + { + include: '#container-query' + } + ] + 'container-name': + begin: '\\G' + end: ''' + (?xi) + (?=(?:\\(|not)|style\\() + # Ends when `(`, `not`, or `style(` is reached + ''' + patterns: [ + { + include: '#comment-block' + } + { + captures: + '1': + name: 'invalid.illegal.constant.container-name.container.css' + '2': + name: 'support.constant.container-name.container.css' + match: ''' + (?xi) + (?<=^|\\s|\\*/) + (?: + # Invalid name + (default|none) + | + # Valid names + ([a-zA-Z0-9\\-_\\\\]+) + ) + (?=$|[({\\s;]|/\\*) + ''' + } + ] + 'container-query': + begin: '((?<=\\s)\\()|(style)(\\()' + beginCaptures: + '1': + name: 'punctuation.definition.parameters.begin.bracket.round.css' + '2': + name: 'support.style-query.container.css' + '3': + name: 'punctuation.definition.parameters.begin.bracket.round.css' + end: '\\)' + endCaptures: + '0': + name: 'punctuation.definition.parameters.end.bracket.round.css' + patterns: [ + { + begin: ''' + (?xi) + (?<=\\s\\() + # Rules for size + ''' + end: '(?=\\))' + patterns: [ + { + include: '#container-query-features' + } + { + include: '#container-size-features' + } + { + include: '#container-size-feature-keywords' + } + ] + } + { + begin: ''' + (?xi) + (?<=style\\() + # Rules for container-query + ''' + end: '(?=\\))' + name: 'style-query.container.css' + patterns: [ + { + include: '#container-query-features' + } + { + include: '#container-style-features' + } + { + include: '#container-style-feature-keywords' + } + ] + } + ] + 'container-query-features': + patterns: [ + { + match: ':' + name: 'punctuation.separator.key-value.css' + } + { + match: '>=|<=|=|<|>' + name: 'keyword.operator.comparison.css' + } + { + include: '#numeric-values' + } + { + include: '#comment-block' + } + ] + 'container-size-features': + match: ''' + (?xi) + (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment + (?: + # Standardised features + ( + (?:min-|max-)? # Range features + (?: aspect-ratio + | block-size + | height + | inline-size + | width + ) + | orientation + ) + ) + (?=\\s|$|[><:=]|\\)|/\\*) # Terminates cleanly + ''' + name: 'support.size.property-name.container.css' + 'container-size-feature-keywords': + patterns: [ + { + match: ''' + (?xi) + (?<=^|\\s|:|\\*/) + # Preceded by whitespace, colon, or comment + (?: portrait + | landscape + # Orientation + ) + (?=\\s|\\)|$) + ''' + name: 'support.constant.property-value.container.css' + } + { + include: '#functions' + } + ] + 'container-style-features': + match: ''' + (?x) + # Custom Property Name + (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment + (?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + match: ''' + (?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + ) + (?=\\s|\\)|$) + ''' + name: 'support.constant.property-value.css' + 'media-query': + begin: '\\G' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#media-types' + } + { + match: '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + name: 'keyword.operator.logical.$1.media.css' + } + { + match: '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + name: 'keyword.operator.logical.and.media.css' + } + { + match: ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + name: 'invalid.illegal.comma.css' + } + { + include: '#commas' + } + { + begin: '\\(' + beginCaptures: + '0': + name: 'punctuation.definition.parameters.begin.bracket.round.css' + end: '\\)' + endCaptures: + '0': + name: 'punctuation.definition.parameters.end.bracket.round.css' + patterns: [ + { + include: '#media-features' + } + { + include: '#media-feature-keywords' + } + { + match: ':' + name: 'punctuation.separator.key-value.css' + } + { + match: '>=|<=|=|<|>' + name: 'keyword.operator.comparison.css' + } + { + captures: + '1': + name: 'constant.numeric.css' + '2': + name: 'keyword.operator.arithmetic.css' + '3': + name: 'constant.numeric.css' + match: '(\\d+)\\s*(/)\\s*(\\d+)' + name: 'meta.ratio.css' + } + { + include: '#numeric-values' + } + { + include: '#comment-block' + } + { + include: '#functions' + } + ] + } + ] + 'media-query-list': + begin: '(?=\\s*[^{;])' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#media-query' + } + ] + 'media-types': + captures: + '1': + name: 'support.constant.media.css' + '2': + name: 'invalid.deprecated.constant.media.css' + match: ''' + (?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + 'nesting-at-rules': + patterns: [ + { + include: '#commas' + } + { + include: '#nesting-rules' + } + { + include: '#rule-list-innards' + } + ] + 'nesting-rules': + patterns: [ + { + match: ''' + (?xi) (?\\s,.\\#|&){:\\[]|/\\*|$) + ''' + name: 'entity.name.tag.css' + } + { + include: '#property-names' + } + { + include: '#selector-innards' + } + ] + 'nesting-selector': + match: '&' + name: 'entity.name.tag.nesting.selector.css' + 'numeric-values': + patterns: [ + { + captures: + '1': + name: 'punctuation.definition.constant.css' + match: '(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b' + name: 'constant.other.color.rgb-value.hex.css' + } + { + captures: + '1': + name: 'keyword.other.unit.percentage.css' + '2': + name: 'keyword.other.unit.${2:/downcase}.css' + match: ''' + (?xi) (?+~|&] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%(*;+~|&] # - Another selector + | /\\* # - A block comment + ) + ''' + name: 'entity.other.attribute-name.class.css' + } + { + captures: + '1': + name: 'punctuation.definition.entity.css' + '2': + patterns: [ + { + include: '#escapes' + } + ] + match: ''' + (?x) + (\\#) + ( + -? + (?![0-9]) + (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + ) + (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) + ''' + name: 'entity.other.attribute-name.id.css' + } + { + begin: '\\[' + beginCaptures: + '0': + name: 'punctuation.definition.entity.begin.bracket.square.css' + end: '\\]' + endCaptures: + '0': + name: 'punctuation.definition.entity.end.bracket.square.css' + name: 'meta.attribute-selector.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#string' + } + { + captures: + '1': + name: 'storage.modifier.ignore-case.css' + match: '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + captures: + '1': + name: 'string.unquoted.attribute-value.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + include: '#escapes' + } + { + match: '[~|^$*]?=' + name: 'keyword.operator.pattern.css' + } + { + match: '\\|' + name: 'punctuation.separator.css' + } + { + captures: + '1': + name: 'entity.other.namespace-prefix.css' + patterns: [ + { + include: '#escapes' + } + ] + match: ''' + (?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + captures: + '1': + name: 'entity.other.attribute-name.css' + patterns: [ + { + include: '#escapes' + } + ] + match: ''' + (?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + } + { + include: '#pseudo-classes' + } + { + include: '#pseudo-elements' + } + { + include: '#functional-pseudo-classes' + } + { + match: ''' + (?x) (?\\s,.\\#|&){:\\[]|/\\*|$) + ''' + name: 'entity.name.tag.css' + 'unicode-range': + captures: + '0': + name: 'constant.other.unicode-range.css' + '1': + name: 'punctuation.separator.dash.unicode-range.css' + match: '(?>>", + "name": "invalid.deprecated.combinator.css" + }, + { + "match": ">>|>|\\+|~", + "name": "keyword.operator.combinator.css" + } + ] + }, + "commas": { + "match": ",", + "name": "punctuation.separator.list.comma.css" + }, + "comment-block": { + "begin": "/\\*", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.css" + } + }, + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.css" + } + }, + "name": "comment.block.css" + }, + "container-condition": { + "begin": "\\G", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "match": "(?xi)\n (?<=not.*)not\n # Only one `not` in allowed", + "name": "invalid.illegal.multiple-not.container.css" + }, + { + "match": "(?i)(?<=\\s|^|,|\\*/)(and|or|not)(?=\\s|{|/\\*|$)", + "name": "keyword.operator.logical.$1.container.css" + }, + { + "include": "#container-name" + }, + { + "include": "#container-query" + } + ] + }, + "container-name": { + "begin": "\\G", + "end": "(?xi)\n (?=(?:\\(|not)|style\\()\n # Ends when `(`, `not`, or `style(` is reached", + "patterns": [ + { + "include": "#comment-block" + }, + { + "captures": { + "1": { + "name": "invalid.illegal.constant.container-name.container.css" + }, + "2": { + "name": "support.constant.container-name.container.css" + } + }, + "match": "(?xi)\n (?<=^|\\s|\\*/)\n(?:\n # Invalid name\n (default|none)\n |\n # Valid names\n ([a-zA-Z0-9\\-_\\\\]+)\n )\n(?=$|[({\\s;]|/\\*)" + } + ] + }, + "container-query": { + "begin": "((?<=\\s)\\()|(style)(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.parameters.begin.bracket.round.css" + }, + "2": { + "name": "support.style-query.container.css" + }, + "3": { + "name": "punctuation.definition.parameters.begin.bracket.round.css" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.css" + } + }, + "patterns": [ + { + "begin": "(?xi)\n (?<=\\s\\()\n # Rules for size ", + "end": "(?=\\))", + "patterns": [ + { + "include": "#container-query-features" + }, + { + "include": "#container-size-features" + }, + { + "include": "#container-size-feature-keywords" + } + ] + }, + { + "begin": "(?xi)\n (?<=style\\()\n # Rules for container-query ", + "end": "(?=\\))", + "name": "style-query.container.css", + "patterns": [ + { + "include": "#container-query-features" + }, + { + "include": "#container-style-features" + }, + { + "include": "#container-style-feature-keywords" + } + ] + } + ] + }, + "container-query-features": { + "patterns": [ + { + "match": ":", + "name": "punctuation.separator.key-value.css" + }, + { + "match": ">=|<=|=|<|>", + "name": "keyword.operator.comparison.css" + }, + { + "include": "#numeric-values" + }, + { + "include": "#comment-block" + } + ] + }, + "container-size-features": { + "match": "(?xi)\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?:\n # Standardised features\n (\n (?:min-|max-)? # Range features\n (?: aspect-ratio\n | block-size\n | height\n | inline-size\n | width\n )\n | orientation\n )\n )\n (?=\\s|$|[><:=]|\\)|/\\*) # Terminates cleanly", + "name": "support.size.property-name.container.css" + }, + "container-size-feature-keywords": { + "patterns": [ + { + "match": "(?xi)\n (?<=^|\\s|:|\\*/)\n # Preceded by whitespace, colon, or comment\n (?: portrait\n | landscape\n # Orientation\n )\n (?=\\s|\\)|$)", + "name": "support.constant.property-value.container.css" + }, + { + "include": "#functions" + } + ] + }, + "container-style-features": { + "match": "(?x)\n # Custom Property Name\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?<:=]|\\)|/\\*) # Terminates cleanly" + }, + "media-feature-keywords": { + "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)", + "name": "support.constant.property-value.css" + }, + "media-query": { + "begin": "\\G", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#media-types" + }, + { + "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)", + "name": "keyword.operator.logical.$1.media.css" + }, + { + "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)", + "name": "keyword.operator.logical.and.media.css" + }, + { + "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))", + "name": "invalid.illegal.comma.css" + }, + { + "include": "#commas" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.css" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.css" + } + }, + "patterns": [ + { + "include": "#media-features" + }, + { + "include": "#media-feature-keywords" + }, + { + "match": ":", + "name": "punctuation.separator.key-value.css" + }, + { + "match": ">=|<=|=|<|>", + "name": "keyword.operator.comparison.css" + }, + { + "captures": { + "1": { + "name": "constant.numeric.css" + }, + "2": { + "name": "keyword.operator.arithmetic.css" + }, + "3": { + "name": "constant.numeric.css" + } + }, + "match": "(\\d+)\\s*(/)\\s*(\\d+)", + "name": "meta.ratio.css" + }, + { + "include": "#numeric-values" + }, + { + "include": "#comment-block" + }, + { + "include": "#functions" + } + ] + } + ] + }, + "media-query-list": { + "begin": "(?=\\s*[^{;])", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#media-query" + } + ] + }, + "media-types": { + "captures": { + "1": { + "name": "support.constant.media.css" + }, + "2": { + "name": "invalid.deprecated.constant.media.css" + } + }, + "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)" + }, + "nesting-at-rules": { + "patterns": [ + { + "include": "#commas" + }, + { + "include": "#nesting-rules" + }, + { + "include": "#rule-list-innards" + } + ] + }, + "nesting-rules": { + "patterns": [ + { + "match": "(?xi) (?\\s,.\\#|&){:\\[]|/\\*|$)", + "name": "entity.name.tag.css" + }, + { + "include": "#property-names" + }, + { + "include": "#selector-innards" + } + ] + }, + "nesting-selector": { + "match": "&", + "name": "entity.name.tag.nesting.selector.css" + }, + "numeric-values": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.constant.css" + } + }, + "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b", + "name": "constant.other.color.rgb-value.hex.css" + }, + { + "captures": { + "1": { + "name": "keyword.other.unit.percentage.css" + }, + "2": { + "name": "keyword.other.unit.${2:/downcase}.css" + } + }, + "match": "(?xi) (?+~|&] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%(*;+~|&] # - Another selector\n | /\\* # - A block comment\n)", + "name": "entity.other.attribute-name.class.css" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.css" + }, + "2": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*)", + "name": "entity.other.attribute-name.id.css" + }, + { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.entity.begin.bracket.square.css" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.entity.end.bracket.square.css" + } + }, + "name": "meta.attribute-selector.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#string" + }, + { + "captures": { + "1": { + "name": "storage.modifier.ignore-case.css" + } + }, + "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)" + }, + { + "captures": { + "1": { + "name": "string.unquoted.attribute-value.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)" + }, + { + "include": "#escapes" + }, + { + "match": "[~|^$*]?=", + "name": "keyword.operator.pattern.css" + }, + { + "match": "\\|", + "name": "punctuation.separator.css" + }, + { + "captures": { + "1": { + "name": "entity.other.namespace-prefix.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)" + }, + { + "captures": { + "1": { + "name": "entity.other.attribute-name.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)" + } + ] + }, + { + "include": "#pseudo-classes" + }, + { + "include": "#pseudo-elements" + }, + { + "include": "#functional-pseudo-classes" + }, + { + "match": "(?x) (?\\s,.\\#|&){:\\[]|/\\*|$)", + "name": "entity.name.tag.css" + }, + "unicode-range": { + "captures": { + "0": { + "name": "constant.other.unicode-range.css" + }, + "1": { + "name": "punctuation.separator.dash.unicode-range.css" + } + }, + "match": "(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + css + (?=\\s|:|$) +''' +patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#combinators' + } + { + include: '#selector' + } + { + include: '#at-rules' + } + { + include: '#rule-list' + } +] +repository: + 'at-rules': + patterns: [ + { + # @charset, with possible preceding BOM sequence + begin: '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' + end: ';|(?=$)' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.charset.css' + patterns: [ + { + captures: + '1': + name: 'invalid.illegal.not-lowercase.charset.css' + '2': + name: 'invalid.illegal.leading-whitespace.charset.css' + '3': + name: 'invalid.illegal.no-whitespace.charset.css' + '4': + name: 'invalid.illegal.whitespace.charset.css' + '5': + name: 'invalid.illegal.not-double-quoted.charset.css' + '6': + name: 'invalid.illegal.unclosed-string.charset.css' + '7': + name: 'invalid.illegal.unexpected-characters.charset.css' + match: '''(?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + captures: + '1': + name: 'keyword.control.at-rule.charset.css' + '2': + name: 'punctuation.definition.keyword.css' + match: '((@)charset)(?=\\s)' + } + { + begin: '"' + beginCaptures: + '0': + name: 'punctuation.definition.string.begin.css' + end: '"|$' + endCaptures: + '0': + name: 'punctuation.definition.string.end.css' + name: 'string.quoted.double.css' + patterns: [ + { + begin: '(?:\\G|^)(?=(?:[^"])+$)' + end: '$' + name: 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + } + { + # @import + begin: '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + beginCaptures: + '1': + name: 'keyword.control.at-rule.import.css' + '2': + name: 'punctuation.definition.keyword.css' + end: ';' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.import.css' + patterns: [ + { + begin: '\\G\\s*(?=/\\*)' + end: '(?<=\\*/)\\s*' + patterns: [ + { + include: '#comment-block' + } + ] + } + { + include: '#string' + } + { + include: '#url' + } + { + include: '#media-query-list' + } + ] + } + { + # @font-face + begin: '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + beginCaptures: + '1': + name: 'keyword.control.at-rule.font-face.css' + '2': + name: 'punctuation.definition.keyword.css' + end: '(?!\\G)' + name: 'meta.at-rule.font-face.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#rule-list' + } + ] + } + { + # @page + begin: '(?i)(@)page(?=[\\s:{]|/\\*|$)' + captures: + '0': + name: 'keyword.control.at-rule.page.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*($|[:{;]))' + name: 'meta.at-rule.page.css' + patterns: [ + { + include: '#rule-list' + } + ] + } + { + # @media + begin: '(?i)(?=@media(\\s|\\(|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)media' + beginCaptures: + '0': + name: 'keyword.control.at-rule.media.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*[{;])' + name: 'meta.at-rule.media.header.css' + patterns: [ + { + include: '#media-query-list' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.media.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.media.end.bracket.curly.css' + name: 'meta.at-rule.media.body.css' + patterns: [ + { + include: '$self' + } + ] + } + ] + } + { + # @counter-style + begin: '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)counter-style' + beginCaptures: + '0': + name: 'keyword.control.at-rule.counter-style.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*{)' + name: 'meta.at-rule.counter-style.header.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + captures: + '0': + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + name: 'variable.parameter.style-name.css' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.property-list.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.property-list.end.bracket.curly.css' + name: 'meta.at-rule.counter-style.body.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#rule-list-innards' + } + ] + } + ] + } + { + # @document + begin: '(?i)(?=@document([\\s\'"{;]|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)document' + beginCaptures: + '0': + name: 'keyword.control.at-rule.document.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*[{;])' + name: 'meta.at-rule.document.header.css' + patterns: [ + { + begin: '(?i)(?>>' + name: 'invalid.deprecated.combinator.css' + } + { + match: '>>|>|\\+|~' + name: 'keyword.operator.combinator.css' + } + ] + 'commas': + match: ',' + name: 'punctuation.separator.list.comma.css' + 'comment-block': + begin: '/\\*' + beginCaptures: + '0': + name: 'punctuation.definition.comment.begin.css' + end: '\\*/' + endCaptures: + '0': + name: 'punctuation.definition.comment.end.css' + name: 'comment.block.css' + 'escapes': + patterns: [ + { + match: '\\\\[0-9a-fA-F]{1,6}' + name: 'constant.character.escape.codepoint.css' + } + { + begin: '\\\\$\\s*' + end: '^(?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + match: '''(?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + ) + (?=\\s|\\)|$) + ''' + name: 'support.constant.property-value.css' + 'media-query': + begin: '\\G' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#media-types' + } + { + match: '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + name: 'keyword.operator.logical.$1.media.css' + } + { + match: '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + name: 'keyword.operator.logical.and.media.css' + } + { + match: ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + name: 'invalid.illegal.comma.css' + } + { + include: '#commas' + } + { + begin: '\\(' + beginCaptures: + '0': + name: 'punctuation.definition.parameters.begin.bracket.round.css' + end: '\\)' + endCaptures: + '0': + name: 'punctuation.definition.parameters.end.bracket.round.css' + patterns: [ + { + include: '#media-features' + } + { + include: '#media-feature-keywords' + } + { + match: ':' + name: 'punctuation.separator.key-value.css' + } + { + match: '>=|<=|=|<|>' + name: 'keyword.operator.comparison.css' + } + { + captures: + '1': + name: 'constant.numeric.css' + '2': + name: 'keyword.operator.arithmetic.css' + '3': + name: 'constant.numeric.css' + match: '(\\d+)\\s*(/)\\s*(\\d+)' + name: 'meta.ratio.css' + } + { + include: '#numeric-values' + } + { + include: '#comment-block' + } + ] + } + ] + 'media-query-list': + begin: '(?=\\s*[^{;])' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#media-query' + } + ] + 'media-types': + captures: + '1': + name: 'support.constant.media.css' + '2': + name: 'invalid.deprecated.constant.media.css' + match: '''(?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + 'numeric-values': + patterns: [ + { + captures: + '1': + name: 'punctuation.definition.constant.css' + match: '(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b' + name: 'constant.other.color.rgb-value.hex.css' + } + { + captures: + '1': + name: 'keyword.other.unit.percentage.css' + '2': + name: 'keyword.other.unit.${2:/downcase}.css' + match: '''(?xi) (?+~|] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%&(*;+~|] # - Another selector + | /\\* # - A block comment + ) + ''' + name: 'entity.other.attribute-name.class.css' + } + { + captures: + '1': + name: 'punctuation.definition.entity.css' + '2': + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (\\#) + ( + -? + (?![0-9]) + (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + ) + (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) + ''' + name: 'entity.other.attribute-name.id.css' + } + { + begin: '\\[' + beginCaptures: + '0': + name: 'punctuation.definition.entity.begin.bracket.square.css' + end: '\\]' + endCaptures: + '0': + name: 'punctuation.definition.entity.end.bracket.square.css' + name: 'meta.attribute-selector.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#string' + } + { + captures: + '1': + name: 'storage.modifier.ignore-case.css' + match: '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + captures: + '1': + name: 'string.unquoted.attribute-value.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + include: '#escapes' + } + { + match: '[~|^$*]?=' + name: 'keyword.operator.pattern.css' + } + { + match: '\\|' + name: 'punctuation.separator.css' + } + { + captures: + '1': + name: 'entity.other.namespace-prefix.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + captures: + '1': + name: 'entity.other.attribute-name.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + } + { + include: '#pseudo-classes' + } + { + include: '#pseudo-elements' + } + { + include: '#functional-pseudo-classes' + } + # Custom HTML elements + { + match: '''(?x) (?\\s,.\\#|){:\\[]|/\\*|$) + ''' + name: 'entity.name.tag.css' + 'unicode-range': + captures: + '0': + name: 'constant.other.unicode-range.css' + '1': + name: 'punctuation.separator.dash.unicode-range.css' + match: '(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + css + (?=\\s|:|$) +''' +'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#combinators' + } + { + 'include': '#selector' + } + { + 'include': '#at-rules' + } + { + 'include': '#rule-list' + } +] +'repository': + 'at-rules': + 'patterns': [ + { + # @charset, with possible preceding BOM sequence + 'begin': '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' + 'end': ';|(?=$)' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.charset.css' + 'patterns': [ + { + 'captures': + '1': + 'name': 'invalid.illegal.not-lowercase.charset.css' + '2': + 'name': 'invalid.illegal.leading-whitespace.charset.css' + '3': + 'name': 'invalid.illegal.no-whitespace.charset.css' + '4': + 'name': 'invalid.illegal.whitespace.charset.css' + '5': + 'name': 'invalid.illegal.not-double-quoted.charset.css' + '6': + 'name': 'invalid.illegal.unclosed-string.charset.css' + '7': + 'name': 'invalid.illegal.unexpected-characters.charset.css' + 'match': '''(?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + 'captures': + '1': + 'name': 'keyword.control.at-rule.charset.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'match': '((@)charset)(?=\\s)' + } + { + 'begin': '"' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.string.begin.css' + 'end': '"|$' + 'endCaptures': + '0': + 'name': 'punctuation.definition.string.end.css' + 'name': 'string.quoted.double.css' + 'patterns': [ + { + 'begin': '(?:\\G|^)(?=(?:[^"])+$)' + 'end': '$' + 'name': 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + } + { + # @import + 'begin': '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.import.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': ';' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.import.css' + 'patterns': [ + { + 'begin': '\\G\\s*(?=/\\*)' + 'end': '(?<=\\*/)\\s*' + 'patterns': [ + { + 'include': '#comment-block' + } + ] + } + { + 'include': '#string' + } + { + 'include': '#url' + } + { + 'include': '#media-query-list' + } + ] + } + { + # @font-face + 'begin': '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.font-face.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?!\\G)' + 'name': 'meta.at-rule.font-face.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list' + } + ] + } + { + # @page + 'begin': '(?i)(@)page(?=[\\s:{]|/\\*|$)' + 'captures': + '0': + 'name': 'keyword.control.at-rule.page.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*($|[:{;]))' + 'name': 'meta.at-rule.page.css' + 'patterns': [ + { + 'include': '#rule-list' + } + ] + } + { + # @media + 'begin': '(?i)(?=@media(\\s|\\(|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)media' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.media.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.media.header.css' + 'patterns': [ + { + 'include': '#media-query-list' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.media.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.media.end.bracket.curly.css' + 'name': 'meta.at-rule.media.body.css' + 'patterns': [ + { + 'include': '$self' + } + ] + } + ] + } + { + # @counter-style + 'begin': '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)counter-style' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.counter-style.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*{)' + 'name': 'meta.at-rule.counter-style.header.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'captures': + '0': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + 'name': 'variable.parameter.style-name.css' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.property-list.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.property-list.end.bracket.curly.css' + 'name': 'meta.at-rule.counter-style.body.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list-innards' + } + ] + } + ] + } + { + # @document + 'begin': '(?i)(?=@document([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)document' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.document.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.document.header.css' + 'patterns': [ + { + 'begin': '(?i)(?>>' + 'name': 'invalid.deprecated.combinator.css' + } + { + 'match': '>>|>|\\+|~' + 'name': 'keyword.operator.combinator.css' + } + ] + 'commas': + 'match': ',' + 'name': 'punctuation.separator.list.comma.css' + 'comment-block': + 'begin': '/\\*' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.comment.begin.css' + 'end': '\\*/' + 'endCaptures': + '0': + 'name': 'punctuation.definition.comment.end.css' + 'name': 'comment.block.css' + 'escapes': + 'patterns': [ + { + 'match': '\\\\[0-9a-fA-F]{1,6}' + 'name': 'constant.character.escape.codepoint.css' + } + { + 'begin': '\\\\$\\s*' + 'end': '^(?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + 'match': '''(?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + ) + (?=\\s|\\)|$) + ''' + 'name': 'support.constant.property-value.css' + 'media-query': + 'begin': '\\G' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#media-types' + } + { + 'match': '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + 'name': 'keyword.operator.logical.$1.media.css' + } + { + 'match': '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + 'name': 'keyword.operator.logical.and.media.css' + } + { + 'match': ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + 'name': 'invalid.illegal.comma.css' + } + { + 'include': '#commas' + } + { + 'begin': '\\(' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.parameters.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.parameters.end.bracket.round.css' + 'patterns': [ + { + 'include': '#media-features' + } + { + 'include': '#media-feature-keywords' + } + { + 'match': ':' + 'name': 'punctuation.separator.key-value.css' + } + { + 'match': '>=|<=|=|<|>' + 'name': 'keyword.operator.comparison.css' + } + { + 'captures': + '1': + 'name': 'constant.numeric.css' + '2': + 'name': 'keyword.operator.arithmetic.css' + '3': + 'name': 'constant.numeric.css' + 'match': '(\\d+)\\s*(/)\\s*(\\d+)' + 'name': 'meta.ratio.css' + } + { + 'include': '#numeric-values' + } + { + 'include': '#comment-block' + } + ] + } + ] + 'media-query-list': + 'begin': '(?=\\s*[^{;])' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#media-query' + } + ] + 'media-types': + 'captures': + '1': + 'name': 'support.constant.media.css' + '2': + 'name': 'invalid.deprecated.constant.media.css' + 'match': '''(?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + 'numeric-values': + 'patterns': [ + { + 'captures': + '1': + 'name': 'punctuation.definition.constant.css' + 'match': '(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b' + 'name': 'constant.other.color.rgb-value.hex.css' + } + { + 'captures': + '1': + 'name': 'keyword.other.unit.percentage.css' + '2': + 'name': 'keyword.other.unit.${2:/downcase}.css' + 'match': '''(?xi) (?+~|] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%&(*;+~|] # - Another selector + | /\\* # - A block comment + ) + ''' + 'name': 'entity.other.attribute-name.class.css' + } + { + 'captures': + '1': + 'name': 'punctuation.definition.entity.css' + '2': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (\\#) + ( + -? + (?![0-9]) + (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + ) + (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) + ''' + 'name': 'entity.other.attribute-name.id.css' + } + { + 'begin': '\\[' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.entity.begin.bracket.square.css' + 'end': '\\]' + 'endCaptures': + '0': + 'name': 'punctuation.definition.entity.end.bracket.square.css' + 'name': 'meta.attribute-selector.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#string' + } + { + 'captures': + '1': + 'name': 'storage.modifier.ignore-case.css' + 'match': '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + 'captures': + '1': + 'name': 'string.unquoted.attribute-value.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + 'include': '#escapes' + } + { + 'match': '[~|^$*]?=' + 'name': 'keyword.operator.pattern.css' + } + { + 'match': '\\|' + 'name': 'punctuation.separator.css' + } + { + 'captures': + '1': + 'name': 'entity.other.namespace-prefix.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + 'captures': + '1': + 'name': 'entity.other.attribute-name.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + } + { + 'include': '#pseudo-classes' + } + { + 'include': '#pseudo-elements' + } + { + 'include': '#functional-pseudo-classes' + } + # Custom HTML elements + { + 'match': '''(?x) (?\\s,.\\#|){:\\[]|/\\*|$) + ''' + 'name': 'entity.name.tag.css' + 'unicode-range': + 'captures': + '0': + 'name': 'constant.other.unicode-range.css' + '1': + 'name': 'punctuation.separator.dash.unicode-range.css' + 'match': '(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n css\n(?=\\s|:|$)", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#combinators" + }, + { + "include": "#selector" + }, + { + "include": "#at-rules" + }, + { + "include": "#rule-list" + } + ], + "repository": { + "at-rules": { + "patterns": [ + { + "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))", + "end": ";|(?=$)", + "endCaptures": { + "0": { + "name": "punctuation.terminator.rule.css" + } + }, + "name": "meta.at-rule.charset.css", + "patterns": [ + { + "captures": { + "1": { + "name": "invalid.illegal.not-lowercase.charset.css" + }, + "2": { + "name": "invalid.illegal.leading-whitespace.charset.css" + }, + "3": { + "name": "invalid.illegal.no-whitespace.charset.css" + }, + "4": { + "name": "invalid.illegal.whitespace.charset.css" + }, + "5": { + "name": "invalid.illegal.not-double-quoted.charset.css" + }, + "6": { + "name": "invalid.illegal.unclosed-string.charset.css" + }, + "7": { + "name": "invalid.illegal.unexpected-characters.charset.css" + } + }, + "match": "(?x) # Possible errors:\n\\G\n((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive)\n|\n\\G(\\s+) # Preceding whitespace\n|\n(@charset\\S[^;]*) # No whitespace after @charset\n|\n(?<=@charset) # Before quoted charset name\n(\\x20{2,}|\\t+) # More than one space used, or a tab\n|\n(?<=@charset\\x20) # Beginning of charset name\n([^\";]+) # Not double-quoted\n|\n(\"[^\"]+$) # Unclosed quote\n|\n(?<=\") # After charset name\n([^;]+) # Unexpected junk instead of semicolon" + }, + { + "captures": { + "1": { + "name": "keyword.control.at-rule.charset.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "match": "((@)charset)(?=\\s)" + }, + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.css" + } + }, + "end": "\"|$", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.css" + } + }, + "name": "string.quoted.double.css", + "patterns": [ + { + "begin": "(?:\\G|^)(?=(?:[^\"])+$)", + "end": "$", + "name": "invalid.illegal.unclosed.string.css" + } + ] + } + ] + }, + { + "begin": "(?i)((@)import)(?:\\s+|$|(?=['\"]|/\\*))", + "beginCaptures": { + "1": { + "name": "keyword.control.at-rule.import.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": ";", + "endCaptures": { + "0": { + "name": "punctuation.terminator.rule.css" + } + }, + "name": "meta.at-rule.import.css", + "patterns": [ + { + "begin": "\\G\\s*(?=/\\*)", + "end": "(?<=\\*/)\\s*", + "patterns": [ + { + "include": "#comment-block" + } + ] + }, + { + "include": "#string" + }, + { + "include": "#url" + }, + { + "include": "#media-query-list" + } + ] + }, + { + "begin": "(?i)((@)font-face)(?=\\s*|{|/\\*|$)", + "beginCaptures": { + "1": { + "name": "keyword.control.at-rule.font-face.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?!\\G)", + "name": "meta.at-rule.font-face.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#rule-list" + } + ] + }, + { + "begin": "(?i)(@)page(?=[\\s:{]|/\\*|$)", + "captures": { + "0": { + "name": "keyword.control.at-rule.page.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*($|[:{;]))", + "name": "meta.at-rule.page.css", + "patterns": [ + { + "include": "#rule-list" + } + ] + }, + { + "begin": "(?i)(?=@media(\\s|\\(|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)media", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.media.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.media.header.css", + "patterns": [ + { + "include": "#media-query-list" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.media.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.media.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.media.body.css", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?i)(?=@counter-style([\\s'\"{;]|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)counter-style", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.counter-style.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*{)", + "name": "meta.at-rule.counter-style.header.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "captures": { + "0": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter\n(?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n |\\\\(?:[0-9a-fA-F]{1,6}|.)\n)*", + "name": "variable.parameter.style-name.css" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.property-list.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.property-list.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.counter-style.body.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#rule-list-innards" + } + ] + } + ] + }, + { + "begin": "(?i)(?=@document([\\s'\"{;]|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)document", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.document.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.document.header.css", + "patterns": [ + { + "begin": "(?i)(?>>", + "name": "invalid.deprecated.combinator.css" + }, + { + "match": ">>|>|\\+|~", + "name": "keyword.operator.combinator.css" + } + ] + }, + "commas": { + "match": ",", + "name": "punctuation.separator.list.comma.css" + }, + "comment-block": { + "begin": "/\\*", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.css" + } + }, + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.css" + } + }, + "name": "comment.block.css" + }, + "escapes": { + "patterns": [ + { + "match": "\\\\[0-9a-fA-F]{1,6}", + "name": "constant.character.escape.codepoint.css" + }, + { + "begin": "\\\\$\\s*", + "end": "^(?<:=]|\\)|/\\*) # Terminates cleanly" + }, + "media-feature-keywords": { + "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)", + "name": "support.constant.property-value.css" + }, + "media-query": { + "begin": "\\G", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#media-types" + }, + { + "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)", + "name": "keyword.operator.logical.$1.media.css" + }, + { + "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)", + "name": "keyword.operator.logical.and.media.css" + }, + { + "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))", + "name": "invalid.illegal.comma.css" + }, + { + "include": "#commas" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.css" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.css" + } + }, + "patterns": [ + { + "include": "#media-features" + }, + { + "include": "#media-feature-keywords" + }, + { + "match": ":", + "name": "punctuation.separator.key-value.css" + }, + { + "match": ">=|<=|=|<|>", + "name": "keyword.operator.comparison.css" + }, + { + "captures": { + "1": { + "name": "constant.numeric.css" + }, + "2": { + "name": "keyword.operator.arithmetic.css" + }, + "3": { + "name": "constant.numeric.css" + } + }, + "match": "(\\d+)\\s*(/)\\s*(\\d+)", + "name": "meta.ratio.css" + }, + { + "include": "#numeric-values" + }, + { + "include": "#comment-block" + } + ] + } + ] + }, + "media-query-list": { + "begin": "(?=\\s*[^{;])", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#media-query" + } + ] + }, + "media-types": { + "captures": { + "1": { + "name": "support.constant.media.css" + }, + "2": { + "name": "invalid.deprecated.constant.media.css" + } + }, + "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)" + }, + "numeric-values": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.constant.css" + } + }, + "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b", + "name": "constant.other.color.rgb-value.hex.css" + }, + { + "captures": { + "1": { + "name": "keyword.other.unit.percentage.css" + }, + "2": { + "name": "keyword.other.unit.${2:/downcase}.css" + } + }, + "match": "(?xi) (?+~|] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%&(*;+~|] # - Another selector\n | /\\* # - A block comment\n)", + "name": "entity.other.attribute-name.class.css" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.css" + }, + "2": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|]|/\\*)", + "name": "entity.other.attribute-name.id.css" + }, + { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.entity.begin.bracket.square.css" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.entity.end.bracket.square.css" + } + }, + "name": "meta.attribute-selector.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#string" + }, + { + "captures": { + "1": { + "name": "storage.modifier.ignore-case.css" + } + }, + "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)" + }, + { + "captures": { + "1": { + "name": "string.unquoted.attribute-value.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)" + }, + { + "include": "#escapes" + }, + { + "match": "[~|^$*]?=", + "name": "keyword.operator.pattern.css" + }, + { + "match": "\\|", + "name": "punctuation.separator.css" + }, + { + "captures": { + "1": { + "name": "entity.other.namespace-prefix.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)" + }, + { + "captures": { + "1": { + "name": "entity.other.attribute-name.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)" + } + ] + }, + { + "include": "#pseudo-classes" + }, + { + "include": "#pseudo-elements" + }, + { + "include": "#functional-pseudo-classes" + }, + { + "match": "(?x) (?\\s,.\\#|){:\\[]|/\\*|$)", + "name": "entity.name.tag.css" + }, + "unicode-range": { + "captures": { + "0": { + "name": "constant.other.unicode-range.css" + }, + "1": { + "name": "punctuation.separator.dash.unicode-range.css" + } + }, + "match": "(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + css + (?=\\s|:|$) +''' +patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#invalid' + } + { + include: '#selector' # Only original callback (2nd one now in #properties-list-innards) + } + { + include: '#at-rules' + } + { + include: '#properties-list' + } +] +repository: + 'at-rules': + patterns: [ + { + include: '#at-rule-charset' + } + { + include: '#at-rule-import' + } + { + include: '#at-rule-font-face' + } + { + include: '#at-rule-page' + } + { + include: '#at-rule-media' + } + { + include: '#at-rule-counter-style' + } + { + # Deprecated + include: '#at-rule-document' + } + { + include: '#at-rule-keyframes' + } + { + include: '#at-rule-supports' + } + { + # Deprecated + include: '#at-rule-viewport' + } + { + include: '#at-rule-font-feature-values' + } + { + # @annotation, @character-variant, @ornaments, @styleset, @stylistic, @swash + include: '#at-feature-value-blocks' + } + { + include: '#at-rule-namespace' + } + { + include: '#at-rule-custom-statement' + } + { + include: '#at-rule-custom-block' + } + ] + ## STATEMENT-STYLE AT-RULES: e.g. `@rule RULE;` + 'at-rule-charset': + begin: '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' # @charset, with possible preceding BOM sequence + end: ';|(?=$)' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.charset.css' + patterns: [ + { + captures: + '1': + name: 'invalid.illegal.not-lowercase.charset.css' + '2': + name: 'invalid.illegal.leading-whitespace.charset.css' + '3': + name: 'invalid.illegal.no-whitespace.charset.css' + '4': + name: 'invalid.illegal.whitespace.charset.css' + '5': + name: 'invalid.illegal.not-double-quoted.charset.css' + '6': + name: 'invalid.illegal.unclosed-string.charset.css' + '7': + name: 'invalid.illegal.unexpected-characters.charset.css' + match: '''(?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + captures: + '1': + name: 'keyword.control.at-rule.charset.css' + '2': + name: 'punctuation.definition.keyword.css' + match: '((@)charset)(?=\\s)' + } + { + begin: '"' + beginCaptures: + '0': + name: 'punctuation.definition.string.begin.css' + end: '"|$' + endCaptures: + '0': + name: 'punctuation.definition.string.end.css' + name: 'string.quoted.double.css' + patterns: [ + { + begin: '(?:\\G|^)(?=(?:[^"])+$)' + end: '$' + name: 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + 'at-rule-import': + begin: '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + beginCaptures: + '1': + name: 'keyword.control.at-rule.import.css' + '2': + name: 'punctuation.definition.keyword.css' + end: ';' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.import.css' + patterns: [ + { + begin: '\\G\\s*(?=/\\*)' + end: '(?<=\\*/)\\s*' + patterns: [ + { + include: '#comment-block' + } + ] + } + { + include: '#string' + } + { + include: '#function-url' + } + { + include: '#media-query-list' + } + ] + 'at-rule-namespace': + begin: '(?i)((@)namespace)(?=[\\s\'";]|/\\*|$)' + beginCaptures: + '1': + name: 'keyword.control.at-rule.namespace.css' + '2': + name: 'punctuation.definition.keyword.css' + end: ';|(?=[@{])' + endCaptures: + '0': + name: 'punctuation.terminator.rule.css' + name: 'meta.at-rule.namespace.css' + patterns: [ + { + include: '#function-url' + } + { + captures: + '1': + patterns: [ + { + include: '#comment-block' + } + ] + '2': + name: 'entity.name.function.namespace-prefix.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?xi) + (?:\\G|^|(?<=\\s)) + (?= + (?<=\\s|^) # Starts with whitespace + (?:[-a-zA-Z_]|[^\\x00-\\x7F]) # Then a valid identifier character + | + \\s* # Possible adjoining whitespace + /\\*(?:[^*]|\\*[^/])*\\*/ # Injected comment + ) + (.*?) # Grouped to embed #comment-block + ( + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ) + ''' + } + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#string' + } + ] + ## BLOCK-STYLE AT-RULES: e.g. `@rule RULE { ... }` + 'at-rule-counter-style': + begin: '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)counter-style' + beginCaptures: + '0': + name: 'keyword.control.at-rule.counter-style.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*{)' + name: 'meta.at-rule.counter-style.header.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + captures: + '0': + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + name: 'variable.parameter.style-name.css' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.property-list.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.property-list.end.bracket.curly.css' + name: 'meta.at-rule.counter-style.body.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#properties-list-innards' + } + ] + } + ] + 'at-rule-font-face': + begin: '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + beginCaptures: + '1': + name: 'keyword.control.at-rule.font-face.css' + '2': + name: 'punctuation.definition.keyword.css' + end: '(?!\\G)' + name: 'meta.at-rule.font-face.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#properties-list' + } + ] + 'at-rule-font-feature-values': + begin: '(?i)((@)font-feature-values)(?=[\\s\'"{;]|/\\*|$)\\s*' + beginCaptures: + '1': + name: 'keyword.control.at-rule.font-feature-values.css' + '2': + name: 'punctuation.definition.keyword.css' + contentName: 'variable.parameter.font-name.css' + end: '(?=\\s*[@{;])' + name: 'meta.at-rule.font-features.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + ] + 'at-rule-keyframes': + begin: '(?i)(?=@(?:-(?:webkit|moz|o|ms)-)?keyframes([\\s\'"{;]|/\\*|$))' + end: '(?<=})(?!\\G)' + patterns: [ + { + begin: '(?i)\\G(@)(?:-(?:webkit|moz|o|ms)-)?keyframes' + beginCaptures: + '0': + name: 'keyword.control.at-rule.keyframes.css' + '1': + name: 'punctuation.definition.keyword.css' + end: '(?=\\s*{)' + name: 'meta.at-rule.keyframes.header.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + captures: + '0': + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + name: 'variable.parameter.keyframe-list.css' + } + ] + } + { + begin: '{' + beginCaptures: + '0': + name: 'punctuation.section.keyframes.begin.bracket.curly.css' + end: '}' + endCaptures: + '0': + name: 'punctuation.section.keyframes.end.bracket.curly.css' + name: 'meta.at-rule.keyframes.body.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + captures: + '1': + name: 'entity.other.keyframe-offset.css' + '2': + name: 'entity.other.keyframe-offset.percentage.css' + match: '''(?xi) + (?>>' + name: 'invalid.deprecated.combinator.css' + } + ] + + 'function-calc': + begin: '(?i)(?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + match: '''(?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + | reduce # Accessibility + ) + (?=\\s|\\)|$) + ''' + name: 'support.constant.property-value.css' + 'media-query': + begin: '\\G' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#comment-block' + } + { + include: '#escapes' + } + { + include: '#media-types' + } + { + match: '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + name: 'keyword.operator.logical.$1.media.css' + } + { + match: '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + name: 'keyword.operator.logical.and.media.css' + } + { + match: ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + name: 'invalid.illegal.comma.css' + } + { + include: '#commas' + } + { + begin: '\\(' + beginCaptures: + '0': + name: 'punctuation.definition.parameters.begin.bracket.round.css' + end: '\\)' + endCaptures: + '0': + name: 'punctuation.definition.parameters.end.bracket.round.css' + patterns: [ + { + include: '#media-features' + } + { + include: '#media-feature-keywords' + } + { + match: ':' + name: 'punctuation.separator.key-value.css' + } + { + match: '>=|<=|=|<|>' + name: 'keyword.operator.comparison.css' + } + { + captures: + '1': + name: 'constant.numeric.css' + '2': + name: 'keyword.operator.arithmetic.css' + '3': + name: 'constant.numeric.css' + match: '(\\d+)\\s*(/)\\s*(\\d+)' + name: 'meta.ratio.css' + } + { + include: '#property-value-numeric' + } + { + include: '#comment-block' + } + { + include: '#property-value-functions' + } + ] + } + ] + 'media-query-list': + begin: '(?=\\s*[^{;])' + end: '(?=\\s*[{;])' + patterns: [ + { + include: '#media-query' + } + ] + 'media-types': + captures: + '1': + name: 'support.constant.media.css' + '2': + name: 'invalid.deprecated.constant.media.css' + match: '''(?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + + 'properties': + patterns: [ + { + include: '#property-name-variable' + } + { + begin: '(?~+] # Selector combinator + | \\| # Selector namespace separator + | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon (which denotes a property) + | ; # Semicolon (condensed property list syntax) + | [{}] # Opening or closing brace (condensed property list syntax) + | \\*/ # Comment end + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + ) + + # Match may contain any of the following: + (?= + (?: + # Match must start with one of the following: + & # Nesting selector + | \\* # Universal selector + | [>~+] # Selector combinator + | \\| # Selector namespace separator + | \\[ # Attribute selector opening bracket + | [a-zA-Z] # Letter + | [^\\x00-\\x7F] # Non-ASCII symbols + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + + # Or one of the following symbols, followed by a word character, underscore, or escape sequence: + | (?: + \\. # Class selector + | \\# # ID selector + ) + (?: + [\\w-] # Word character or hyphen + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + ) + + # Or one of the following symbols, followed a letter or hyphen: + | (?: + \\: # Pseudo-class + | \\:\\: # Pseudo-element + ) + [a-zA-Z-] # Letter or hyphen + ) + ) + + # Match must NOT contain any of the following: + (?! + [\\w-]*[\\:]+\\s # One or more colons immediately followed by a whitespace (denotes a property or invalid sequence) + | [^{]*; # Any characters and a semicolon before an opening bracket (denotes the end of a property) + | [^{]*} # A closing bracket before an opening bracket (denotes a property) + | [^\\:]+(\\:\\:):+ # More than two colons (invalid sequence) + | # A single-colon, unless it's part of a pseudo-class or pseudo-element, inside a bracket, or ends with a brace + (?![^;]*\\{) # Ignore the single-colon exclude rule if the colon is followed by an opening curly brace before a semicolon + (?: + [^\\[\\{]*\\: # A single-colon, not preceded by a opening square bracket or curly brace, and not followed by: + (?! + # Allow pseudo-classes: + # NOTE: `left` and `right` are not allowed as pseudo-classes unless they are followed with an opening curly brace since they are used as property values. `default` and `optional` are also not allowed as pseudo-classes unless they are used with specific selectors listed below. + active|any-link|checked|default|disabled|empty|enabled|first + | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover + | in-range|indeterminate|invalid|link|optional|out-of-range + | read-only|read-write|required|root|scope|target|unresolved + | valid|visited + + # Allow functional pseudo-classes: + | (?: dir|lang + | not|has|matches|where|is + | nth-(?:last-)?(?:child|of-type) + )\\( + + # Allow double-colon pseudo-elements: + | \\: + + # Allow single-colon pseudo-elements: + | after + | before + | first-letter + | first-line + | (?: + \\- + (?: + ah|apple|atsc|epub|hp|khtml|moz + | ms|o|rim|ro|tc|wap|webkit|xv + ) + | (?: + mso|prince + ) + ) + -[a-z-]+ + + # Allow single-colon for legacy pseudo-elements: + # NOTE: `content` is not allowed as a legacy pseudo-element unless it's followed with an opening curly brace since it's used as a property value. + | backdrop + | grammar-error + | marker + | placeholder + | selection + | shadow + | spelling-error + ) + + | # Only allow the `default` pseudo-class for specific selectors (otherwise it's a property value): + (?! button|input|option|select ) # Allowed Selectors + [^\\:]+\\:default # `:default` pseudo-class + + | # Only allow the `optional` pseudo-class for specific selectors (otherwise it's a property value): + (?! input|select|textarea ) # Allowed Selectors + [^\\:]+\\:optional # `:optional` pseudo-class + ) + ) + ''' + end: '''(?x) + # Match must end with: + (?= + \\s* # Optional whitespace and one of the following: + (?: + \\/ # Comment + | @ # At-rule + | { # Opening property list brace + | \\) # Closing function brace + | $ # End of line + ) + ) + ''' + name: 'meta.selector.css' + patterns: [ + { + include: '#selector-innards' + } + ] + 'selector-attribute': + begin: '\\[' + beginCaptures: + '0': + name: 'punctuation.definition.entity.begin.bracket.square.css' + end: '\\]' + endCaptures: + '0': + name: 'punctuation.definition.entity.end.bracket.square.css' + name: 'meta.attribute-selector.css' + patterns: [ + { + include: '#comment-block' + } + { + include: '#string' + } + { + captures: + '1': + name: 'storage.modifier.ignore-case.css' + match: '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + captures: + '1': + name: 'string.unquoted.attribute-value.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + include: '#escapes' + } + { + match: '[~|^$*]?=' + name: 'keyword.operator.pattern.css' + } + { + match: '\\|' + name: 'punctuation.separator.css' + } + { + captures: + '1': + name: 'entity.other.namespace-prefix.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + captures: + '1': + name: 'entity.other.attribute-name.css' + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + 'selector-class': + captures: + '1': + name: 'punctuation.definition.entity.css' + '2': + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) + (\\.) # Valid class-name + ( + (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + )+ + ) # Followed by either: + (?= $ # - End of the line + | [\\s,.\\#)\\[:{>+~|&] # - Another selector + | /\\* # - A block comment + ) + ''' + name: 'entity.other.attribute-name.class.css' + 'selector-combinators': + match: '>>|>|\\+|~' + name: 'keyword.operator.combinator.css' + 'selector-custom': + match: '''(?x) (?+~|&]|/\\*) + ''' + name: 'entity.other.attribute-name.id.css' + 'selector-innards': + patterns: [ + { + include: '#comment-block' + } + { + include: '#commas' + } + { + include: '#escapes' + } + { + include: '#invalid' + } + { + include: '#selector-combinators' + } + { + include: '#selector-namespace' + } + { + include: '#selector-tag-names' + } + { + include: '#selector-wildcard' + } + { + include: '#selector-nesting' + } + { + include: '#selector-invalid' + } + { + include: '#selector-class' + } + { + include: '#selector-id' + } + { + include: '#selector-attribute' + } + { + include: '#selector-pseudo-classes' + } + { + include: '#selector-pseudo-elements' + } + { + include: '#selector-functional-pseudo-classes' + } + { + include: '#selector-custom' + } + ] + 'selector-namespace': + captures: + '1': + name: 'entity.other.namespace-prefix.css' + '2': + name: 'punctuation.separator.css' + match: '''(?x) + (?:^|(?<=[\\s,(};])) # Follows whitespace, comma, semicolon, or bracket + (?! + [-\\w*]+ + \\| + (?! + [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match + | [^\\x00-\\x7F] + ) + ) + ( + (?: [-a-zA-Z_] | [^\\x00-\\x7F] ) # First letter + (?: [-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + | \\\\(?:[0-9a-fA-F]{1,6}|.) + )* + | + \\* # Universal namespace + )? + (\\|) # Namespace separator + ''' + 'selector-nesting': + match: '&' + name: 'entity.name.tag.nesting.css' + 'selector-invalid': + captures: + '1': + name: 'punctuation.definition.entity.css' + '2': + patterns: [ + { + include: '#escapes' + } + ] + match: '''(?x) (?+~|&] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%(*;\\s,.\\#|&){:\\[]|/\\*|$) + ''' + name: 'entity.name.tag.css' + 'selector-wildcard': + match: '\\*' + name: 'entity.name.tag.wildcard.css' + + 'string': + patterns: [ + { + begin: '"' + beginCaptures: + '0': + name: 'punctuation.definition.string.begin.css' + end: '"|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + css + (?=\\s|:|$) +''' +'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#combinators' + } + { + 'include': '#selector' + } + { + 'include': '#at-rules' + } + { + 'include': '#rule-list' + } + { + 'include': '#nesting-selector' + } +] +'repository': + 'arithmetic-operators': + 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' + 'name': 'keyword.operator.arithmetic.css' + 'at-rules': + 'patterns': [ + # @container + { + 'begin': '(?i)\\G(@)container' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.container.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.container.header.css' + 'patterns': [ + { + 'include': '#container-condition' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.container.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.container.end.bracket.curly.css' + 'name': 'meta.at-rule.container.body.css' + 'patterns': [ + { + 'include': '#nesting-at-rules' + } + { + 'include': '$self' + } + ] + } + { + # @charset, with possible preceding BOM sequence + 'begin': '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' + 'end': ';|(?=$)' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.charset.css' + 'patterns': [ + { + 'captures': + '1': + 'name': 'invalid.illegal.not-lowercase.charset.css' + '2': + 'name': 'invalid.illegal.leading-whitespace.charset.css' + '3': + 'name': 'invalid.illegal.no-whitespace.charset.css' + '4': + 'name': 'invalid.illegal.whitespace.charset.css' + '5': + 'name': 'invalid.illegal.not-double-quoted.charset.css' + '6': + 'name': 'invalid.illegal.unclosed-string.charset.css' + '7': + 'name': 'invalid.illegal.unexpected-characters.charset.css' + 'match': '''(?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + 'captures': + '1': + 'name': 'keyword.control.at-rule.charset.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'match': '((@)charset)(?=\\s)' + } + { + 'begin': '"' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.string.begin.css' + 'end': '"|$' + 'endCaptures': + '0': + 'name': 'punctuation.definition.string.end.css' + 'name': 'string.quoted.double.css' + 'patterns': [ + { + 'begin': '(?:\\G|^)(?=(?:[^"])+$)' + 'end': '$' + 'name': 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + } + { + # @import + 'begin': '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.import.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': ';' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.import.css' + 'patterns': [ + { + 'begin': '\\G\\s*(?=/\\*)' + 'end': '(?<=\\*/)\\s*' + 'patterns': [ + { + 'include': '#comment-block' + } + ] + } + { + 'include': '#string' + } + { + 'include': '#url' + } + { + 'include': '#media-query-list' + } + ] + } + { + # @font-face + 'begin': '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.font-face.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?!\\G)' + 'name': 'meta.at-rule.font-face.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list' + } + ] + } + { + # @page + 'begin': '(?i)(@)page(?=[\\s:{]|/\\*|$)' + 'captures': + '0': + 'name': 'keyword.control.at-rule.page.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*($|[:{;]))' + 'name': 'meta.at-rule.page.css' + 'patterns': [ + { + 'include': '#rule-list' + } + ] + } + { + # @media + 'begin': '(?i)(?=@media(\\s|\\(|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)media' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.media.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.media.header.css' + 'patterns': [ + { + 'include': '#media-query-list' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.media.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.media.end.bracket.curly.css' + 'name': 'meta.at-rule.media.body.css' + 'patterns': [ + { + 'include': '#nesting-at-rules' + } + { + 'include': '$self' + } + ] + } + ] + } + { + # @counter-style + 'begin': '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)counter-style' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.counter-style.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*{)' + 'name': 'meta.at-rule.counter-style.header.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'captures': + '0': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + 'name': 'variable.parameter.style-name.css' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.property-list.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.property-list.end.bracket.curly.css' + 'name': 'meta.at-rule.counter-style.body.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list-innards' + } + ] + } + ] + } + { + # @document + 'begin': '(?i)(?=@document([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)document' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.document.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.document.header.css' + 'patterns': [ + { + 'begin': '(?i)(?>>' + 'name': 'invalid.deprecated.combinator.css' + } + { + 'match': '>>|>|\\+|~' + 'name': 'keyword.operator.combinator.css' + } + ] + 'commas': + 'match': ',' + 'name': 'punctuation.separator.list.comma.css' + 'comment-block': + 'begin': '/\\*' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.comment.begin.css' + 'end': '\\*/' + 'endCaptures': + '0': + 'name': 'punctuation.definition.comment.end.css' + 'name': 'comment.block.css' + 'container-condition': + 'begin': '\\G' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'match': '(?xi)\n (?<=not.*)not\n # Only one `not` in allowed' + 'name': 'invalid.illegal.multiple-not.container.css' + } + { + 'match': '(?i)(?<=\\s|^|,|\\*/)(and|or|not)(?=\\s|{|/\\*|$)' + 'name': 'keyword.operator.logical.$1.container.css' + } + { + 'include': '#container-name' + } + { + 'include': '#container-query' + } + ] + 'container-name': + 'begin': '\\G' + 'end': '(?xi)\n (?=(?:\\(|not)|style\\()\n # Ends when `(`, `not`, or `style(` is reached' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'captures': + '1': + 'name': 'invalid.illegal.constant.container-name.container.css' + '2': + 'name': 'support.constant.container-name.container.css' + 'match': '(?xi)\n (?<=^|\\s|\\*/)\n(?:\n # Invalid name\n (default|none)\n |\n # Valid names\n ([a-zA-Z0-9\\-_\\\\]+)\n )\n(?=$|[({\\s;]|/\\*)' + } + ] + 'container-query': + 'begin': '((?<=\\s)\\()|(style)(\\()' + 'beginCaptures': + '1': + 'name': 'punctuation.definition.parameters.begin.bracket.round.css' + '2': + 'name': 'support.style-query.container.css' + '3': + 'name': 'punctuation.definition.parameters.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.parameters.end.bracket.round.css' + 'patterns': [ + { + 'begin': '(?xi)\n (?<=\\s\\()\n # Rules for size ' + 'end': '(?=\\))' + 'patterns': [ + { + 'include': '#container-query-features' + } + { + 'include': '#container-size-features' + } + { + 'include': '#container-size-feature-keywords' + } + ] + } + { + 'begin': '(?xi)\n (?<=style\\()\n # Rules for container-query ' + 'end': '(?=\\))' + 'name': 'style-query.container.css' + 'patterns': [ + { + 'include': '#container-query-features' + } + { + 'include': '#container-style-features' + } + { + 'include': '#container-style-feature-keywords' + } + ] + } + ] + 'container-query-features': + 'patterns': [ + { + 'match': ':' + 'name': 'punctuation.separator.key-value.css' + } + { + 'match': '>=|<=|=|<|>' + 'name': 'keyword.operator.comparison.css' + } + { + 'include': '#numeric-values' + } + { + 'include': '#comment-block' + } + ] + 'container-size-features': + 'match': '(?xi)\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?:\n # Standardised features\n (\n (?:min-|max-)? # Range features\n (?: aspect-ratio\n | block-size\n | height\n | inline-size\n | width\n )\n | orientation\n )\n )\n (?=\\s|$|[><:=]|\\)|/\\*) # Terminates cleanly' + 'name': 'support.size.property-name.container.css' + 'container-size-feature-keywords': + 'patterns': [ + { + 'match': '(?xi)\n (?<=^|\\s|:|\\*/)\n # Preceded by whitespace, colon, or comment\n (?: portrait\n | landscape\n # Orientation\n )\n (?=\\s|\\)|$)' + 'name': 'support.constant.property-value.container.css' + } + { + 'include': '#functions' + } + ] + 'container-style-features': + 'match': '(?x)\n # Custom Property Name\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + 'match': '''(?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + ) + (?=\\s|\\)|$) + ''' + 'name': 'support.constant.property-value.css' + 'media-query': + 'begin': '\\G' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#media-types' + } + { + 'match': '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + 'name': 'keyword.operator.logical.$1.media.css' + } + { + 'match': '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + 'name': 'keyword.operator.logical.and.media.css' + } + { + 'match': ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + 'name': 'invalid.illegal.comma.css' + } + { + 'include': '#commas' + } + { + 'begin': '\\(' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.parameters.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.parameters.end.bracket.round.css' + 'patterns': [ + { + 'include': '#media-features' + } + { + 'include': '#media-feature-keywords' + } + { + 'match': ':' + 'name': 'punctuation.separator.key-value.css' + } + { + 'match': '>=|<=|=|<|>' + 'name': 'keyword.operator.comparison.css' + } + { + 'captures': + '1': + 'name': 'constant.numeric.css' + '2': + 'name': 'keyword.operator.arithmetic.css' + '3': + 'name': 'constant.numeric.css' + 'match': '(\\d+)\\s*(/)\\s*(\\d+)' + 'name': 'meta.ratio.css' + } + { + 'include': '#numeric-values' + } + { + 'include': '#comment-block' + } + { + "include": "#functions" + } + ] + } + ] + 'media-query-list': + 'begin': '(?=\\s*[^{;])' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#media-query' + } + ] + 'media-types': + 'captures': + '1': + 'name': 'support.constant.media.css' + '2': + 'name': 'invalid.deprecated.constant.media.css' + 'match': '''(?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + 'nesting-at-rules': + 'patterns': [ + { + 'include': '#commas' + } + { + 'include': '#nesting-rules' + } + { + 'include': '#rule-list-innards' + } + ] + 'nesting-rules': + 'patterns': [ + { + 'match': '(?xi) (?\\s,.\\#|&){:\\[]|/\\*|$)' + 'name': 'entity.name.tag.css' + } + { + 'include': '#property-names' + } + { + 'include': '#selector-innards' + } + ] + 'nesting-selector': + 'match': '&' + 'name': 'entity.name.tag.nesting.selector.css' + 'numeric-values': + 'patterns': [ + { + 'captures': + '1': + 'name': 'punctuation.definition.constant.css' + 'match': '(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b' + 'name': 'constant.other.color.rgb-value.hex.css' + } + { + 'captures': + '1': + 'name': 'keyword.other.unit.percentage.css' + '2': + 'name': 'keyword.other.unit.${2:/downcase}.css' + 'match': '''(?xi) (?+~|&] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%&(*;+~|&] # - Another selector + | /\\* # - A block comment + ) + ''' + 'name': 'entity.other.attribute-name.class.css' + } + { + 'captures': + '1': + 'name': 'punctuation.definition.entity.css' + '2': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (\\#) + ( + -? + (?![0-9]) + (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + ) + (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) + ''' + 'name': 'entity.other.attribute-name.id.css' + } + { + 'begin': '\\[' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.entity.begin.bracket.square.css' + 'end': '\\]' + 'endCaptures': + '0': + 'name': 'punctuation.definition.entity.end.bracket.square.css' + 'name': 'meta.attribute-selector.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#string' + } + { + 'captures': + '1': + 'name': 'storage.modifier.ignore-case.css' + 'match': '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + 'captures': + '1': + 'name': 'string.unquoted.attribute-value.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + 'include': '#escapes' + } + { + 'match': '[~|^$*]?=' + 'name': 'keyword.operator.pattern.css' + } + { + 'match': '\\|' + 'name': 'punctuation.separator.css' + } + { + 'captures': + '1': + 'name': 'entity.other.namespace-prefix.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + 'captures': + '1': + 'name': 'entity.other.attribute-name.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + } + { + 'include': '#pseudo-classes' + } + { + 'include': '#pseudo-elements' + } + { + 'include': '#functional-pseudo-classes' + } + # Custom HTML elements + { + 'match': '''(?x) (?\\s,.\\#|&){:\\[]|/\\*|$) + ''' + 'name': 'entity.name.tag.css' + 'unicode-range': + 'captures': + '0': + 'name': 'constant.other.unicode-range.css' + '1': + 'name': 'punctuation.separator.dash.unicode-range.css' + 'match': '(?\\+\\~\\s\\w-]*\\{ # Don't match if there is a selector opening bracket following the name + + # Don't match if the value immediately following the colon is a pseudo-class + | ( + active|any-link|checked|default|disabled|empty|enabled|first + | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover + | in-range|indeterminate|invalid|left|link|optional|out-of-range + | read-only|read-write|required|right|root|scope|target|unresolved + | valid|visited + | not|has|matches|where|is # Matches (logical) pseudo-classes + | nth-(?:last-)?(?:child|of-type) # Matches (child-indexed) pseudo-classes + ) + + # Don't match if the value immediately following the colon is a pseudo-element + | ( + after + | before + | first-letter + | first-line + | (?:-(?:ah|apple|atsc|epub|hp|khtml|moz + |ms|o|rim|ro|tc|wap|webkit|xv) + | (?:mso|prince)) + -[a-z-]+ + ) + ) + ''' + 'beginCaptures': + '0': + 'name': 'test.begin.0' + '1': + 'name': 'test.begin.1' + + 'end': '''(?x) + \\; + | \\) + | \\} + ''' + 'endCaptures': + '0': + 'name': 'test.end.0' + '1': + 'name': 'test.end.1' + 'contentName': 'meta.property-name.css' + 'patterns': [ + { + 'include': '#property-names' + } + ] diff --git a/.dev-assets/scopes/selector-custom.cson b/.dev-assets/scopes/selector-custom.cson new file mode 100644 index 0000000..a796edc --- /dev/null +++ b/.dev-assets/scopes/selector-custom.cson @@ -0,0 +1,58 @@ +'selector-custom': + 'patterns': [ + { + 'match': '''(?x) + (?~+] + ) + ) + ''' + beginCaptures: + '0': + name: 'test.unterminated.begin' + end: '''(?xi) + \\s* + \\w+ + (:) + \\w+ + | \\{ + | \\} + + # (?<= + # \\{ + # | \\} + # \\w+ + # (:) + # \\w+ + # ) + + # (?! + # \\} + # ) + ''' + endCaptures: + '0': + name: 'test.unterminated.end' + name: 'test.unterminated' diff --git a/.dev-assets/scopes/selector.cson b/.dev-assets/scopes/selector.cson new file mode 100644 index 0000000..9a52371 --- /dev/null +++ b/.dev-assets/scopes/selector.cson @@ -0,0 +1,21 @@ + 'selector': + begin: '''(?x) + (?= + (?:\\|)? # Possible anonymous namespace prefix + (?: + [-\\[:.*&\\#a-zA-Z_] # Valid selector character + | + [^\\x00-\\x7F] # Which can include non-ASCII symbols + | + \\\\ # Or an escape sequence + (?:[0-9a-fA-F]{1,6}|.) + ) + ) + ''' + end: '(?=\\s*[/@{)])' + name: 'meta.selector.css' + patterns: [ + { + include: '#selector-innards' + } + ] diff --git a/.dev-assets/syntax-issues/archive/at-supports.css b/.dev-assets/syntax-issues/archive/at-supports.css deleted file mode 100644 index 1846db0..0000000 --- a/.dev-assets/syntax-issues/archive/at-supports.css +++ /dev/null @@ -1,7 +0,0 @@ -:root { - --supports-red: false; - @supports (color: red) { - --supports-red: true; - } - --x: 1; -} diff --git a/.dev-assets/syntax-issues/bugs/at-supports-issue.css b/.dev-assets/syntax-issues/bugs/at-supports-issue.css new file mode 100644 index 0000000..513b661 --- /dev/null +++ b/.dev-assets/syntax-issues/bugs/at-supports-issue.css @@ -0,0 +1,28 @@ +/* Correctly nested */ +:root { + --supports-red: false; + @supports (color: red) { + --supports-red: true; + width: 100%; + row-gap: 1rem; + section { + display: flex; + row-gap: 1rem; + } + } + --x: 1; +} + +@supports (color: red) { + section { + display: flex; + row-gap: 1rem; + } +} + +/* Invalid first-level properties not nested within a selector should not have syntax highlighting */ +@supports (color: red) { + --supports-red: true; + width: 100%; + row-gap: 1rem; +} diff --git a/.dev-assets/syntax-issues/grid-placement-slash.css b/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css similarity index 58% rename from .dev-assets/syntax-issues/grid-placement-slash.css rename to .dev-assets/syntax-issues/bugs/property-value-slash-issue.css index 94edcaa..12e5bfb 100644 --- a/.dev-assets/syntax-issues/grid-placement-slash.css +++ b/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css @@ -4,6 +4,9 @@ /* Property value slash doesn't have its own syntax highlighting */ .example { - grid-row: 1 / 3; grid-column: 1/-1; + grid-row: 1 / 3; + + width: clamp(100px, calc(30% / 2rem - 10px), 900px); + width: some-edgy-new-function(30% / 2rem + 10px); } diff --git a/.dev-assets/syntax-issues/archive/aspect-ratio.css b/.dev-assets/syntax-issues/fixed/aspect-ratio-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/aspect-ratio.css rename to .dev-assets/syntax-issues/fixed/aspect-ratio-issue.css diff --git a/.dev-assets/syntax-issues/archive/at-page.css b/.dev-assets/syntax-issues/fixed/at-page-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/at-page.css rename to .dev-assets/syntax-issues/fixed/at-page-issue.css diff --git a/.dev-assets/syntax-issues/archive/calc-in-at-media.css b/.dev-assets/syntax-issues/fixed/calc-in-at-media-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/calc-in-at-media.css rename to .dev-assets/syntax-issues/fixed/calc-in-at-media-issue.css diff --git a/.dev-assets/syntax-issues/archive/calc-nesting.css b/.dev-assets/syntax-issues/fixed/calc-nesting-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/calc-nesting.css rename to .dev-assets/syntax-issues/fixed/calc-nesting-issue.css diff --git a/.dev-assets/syntax-issues/archive/clamp-arithmetic-operators.css b/.dev-assets/syntax-issues/fixed/clamp-arithmetic-operators-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/clamp-arithmetic-operators.css rename to .dev-assets/syntax-issues/fixed/clamp-arithmetic-operators-issue.css diff --git a/.dev-assets/syntax-issues/archive/at-container.css b/.dev-assets/syntax-issues/missing/at-container-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/at-container.css rename to .dev-assets/syntax-issues/missing/at-container-issue.css diff --git a/.dev-assets/syntax-issues/at-import.css b/.dev-assets/syntax-issues/missing/at-import-issue.css similarity index 85% rename from .dev-assets/syntax-issues/at-import.css rename to .dev-assets/syntax-issues/missing/at-import-issue.css index c7303fc..9bda8da 100644 --- a/.dev-assets/syntax-issues/at-import.css +++ b/.dev-assets/syntax-issues/missing/at-import-issue.css @@ -3,6 +3,7 @@ */ /* new syntax rules for @import have no syntax highlighting */ +@import "/css/styles.css" supports(color: red); @import url("green.css") layer supports(selector(&)) (min-width: calc(10px)); /* layer as a function */ diff --git a/.dev-assets/syntax-issues/at-layer.css b/.dev-assets/syntax-issues/missing/at-layer-issue.css similarity index 100% rename from .dev-assets/syntax-issues/at-layer.css rename to .dev-assets/syntax-issues/missing/at-layer-issue.css diff --git a/.dev-assets/syntax-issues/at-media-propertie.css b/.dev-assets/syntax-issues/missing/at-media-properties-issue.css similarity index 65% rename from .dev-assets/syntax-issues/at-media-propertie.css rename to .dev-assets/syntax-issues/missing/at-media-properties-issue.css index fcc87c5..8f40324 100644 --- a/.dev-assets/syntax-issues/at-media-propertie.css +++ b/.dev-assets/syntax-issues/missing/at-media-properties-issue.css @@ -2,8 +2,14 @@ * AT MEDIA PROPERTY */ -/* Missing token for `prefers-reduced-motion: reduce` */ -@media (prefers-reduced-motion: reduce) { +/* Missing tokens */ +@media (any-hover: 0) { +} +@media (any-pointer: 0) { +} +@media (color-gamut: 0) { } +@media (prefers-reduced-motion: reduce) { + /* NOTE: see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries#descriptors for more media properties */ diff --git a/.dev-assets/syntax-issues/archive/light-dark-func.css b/.dev-assets/syntax-issues/missing/light-dark-func-issue.css similarity index 100% rename from .dev-assets/syntax-issues/archive/light-dark-func.css rename to .dev-assets/syntax-issues/missing/light-dark-func-issue.css diff --git a/.dev-assets/syntax-issues/of-not.css b/.dev-assets/syntax-issues/missing/of-not-issue.css similarity index 100% rename from .dev-assets/syntax-issues/of-not.css rename to .dev-assets/syntax-issues/missing/of-not-issue.css diff --git a/.dev-assets/syntax-issues/scss-issues.scss b/.dev-assets/syntax-issues/scss-issues.scss new file mode 100644 index 0000000..a9249a8 --- /dev/null +++ b/.dev-assets/syntax-issues/scss-issues.scss @@ -0,0 +1,67 @@ +#footer { + p { + color: var(--color-re); + margin-block-end: 16px; + } +} + +.re-wrap #footer p { + color: var(--color-mc-100); +} + +.footer-blurb { + max-inline-size: 584px; +} + +.footer-copyright { + margin-block-end: 0; + font-size: 16px; +} + +.footer-license { + font-size: 16px; +} + +.footer-notice { + display: flex; + flex-direction: column; + + a { + color: var(--color-mc-100); + margin-block-end: 8px; + font-size: 16px; + text-decoration: none; + } +} + +.connect-btn-wrap { + display: flex; + gap: 16px; +} + +.connect-btn { + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--color-re); + border-radius: var(--border-radius-box); + block-size: 3.125rem; + inline-size: 3.125rem; + margin-block-end: 16px; + text-decoration: none; + + .logo { + opacity: 0.8; + transition: opacity 300ms; + } + + &:hover { + .logo { + opacity: 1; + } + } + + .re-wrap & { + border: 1px solid var(--color-mc-100); + } +} diff --git a/.dev-assets/get-shared-names.js b/.dev-assets/util/get-shared-names.js similarity index 99% rename from .dev-assets/get-shared-names.js rename to .dev-assets/util/get-shared-names.js index 729219a..2a52701 100644 --- a/.dev-assets/get-shared-names.js +++ b/.dev-assets/util/get-shared-names.js @@ -15,7 +15,7 @@ console.log(sharedWords); // LOGS: // color-profile // content -// cursor P +// cursor // filter // font // header diff --git a/.dev-assets/util/get-shared-values.js b/.dev-assets/util/get-shared-values.js new file mode 100644 index 0000000..5f1d54a --- /dev/null +++ b/.dev-assets/util/get-shared-values.js @@ -0,0 +1,197 @@ +(function getSharedValues() { + const valueColorKeywords = ` + aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow + + |aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood + |cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan + |darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange + |darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise + |darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen + |gainsboro|ghostwhite|gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki + |lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow + |lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray + |lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue + |mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise + |mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered + |orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum + |powderblue|rebeccapurple|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell + |sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato + |transparent|turquoise|violet|wheat|whitesmoke|yellowgreen + + |currentColor + + |ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow + |ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption + |InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow + |ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowTex + `.replace(/\s/g, ""); + + const valueKeywords = ` + above|absolute|active|add|additive|after-edge|alias|all|all-petite-caps|all-scroll|all-small-caps|alpha|alphabetic|alternate|alternate-reverse + |always|antialiased|auto|auto-fill|auto-fit|auto-pos|available|avoid|avoid-column|avoid-page|avoid-region|backwards|balance|baseline|before-edge|below|bevel + |bidi-override|blink|block|block-axis|block-start|block-end|bold|bolder|border|border-box|both|bottom|bottom-outside|break-all|break-word|bullets + |butt|capitalize|caption|cell|center|central|char|circle|clip|clone|close-quote|closest-corner|closest-side|col-resize|collapse|color|color-burn + |color-dodge|column|column-reverse|common-ligatures|compact|condensed|contain|content|content-box|contents|context-menu|contextual|copy|cover + |crisp-edges|crispEdges|crosshair|cyclic|dark|darken|dashed|decimal|default|dense|diagonal-fractions|difference|digits|disabled|disc|discretionary-ligatures + |distribute|distribute-all-lines|distribute-letter|distribute-space|dot|dotted|double|double-circle|downleft|downright|e-resize|each-line|ease|ease-in + |ease-in-out|ease-out|economy|ellipse|ellipsis|embed|end|evenodd|ew-resize|exact|exclude|exclusion|expanded|extends|extra-condensed|extra-expanded + |fallback|farthest-corner|farthest-side|fill|fill-available|fill-box|filled|fit-content|fixed|flat|flex|flex-end|flex-start|flip|flow-root|forwards|freeze + |from-image|full-width|geometricPrecision|georgian|grab|grabbing|grayscale|grid|groove|hand|hanging|hard-light|help|hidden|hide + |historical-forms|historical-ligatures|horizontal|horizontal-tb|hue|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space + |ideographic|inactive|infinite|inherit|initial|inline|inline-axis|inline-block|inline-end|inline-flex|inline-grid|inline-list-item|inline-start + |inline-table|inset|inside|inter-character|inter-ideograph|inter-word|intersect|invert|isolate|isolate-override|italic|jis04|jis78|jis83 + |jis90|justify|justify-all|kannada|keep-all|landscape|large|larger|left|light|lighten|lighter|line|line-edge|line-through|linear|linearRGB + |lining-nums|list-item|local|loose|lowercase|lr|lr-tb|ltr|luminance|luminosity|main-size|mandatory|manipulation|manual|margin-box|match-parent + |match-source|mathematical|max-content|medium|menu|message-box|middle|min-content|miter|mixed|move|multiply|n-resize|narrower|ne-resize + |nearest-neighbor|nesw-resize|newspaper|no-change|no-clip|no-close-quote|no-common-ligatures|no-contextual|no-discretionary-ligatures + |no-drop|no-historical-ligatures|no-open-quote|no-repeat|none|nonzero|normal|not-allowed|nowrap|ns-resize|numbers|numeric|nw-resize|nwse-resize + |oblique|oldstyle-nums|open|open-quote|optimizeLegibility|optimizeQuality|optimizeSpeed|optional|ordinal|outset|outside|over|overlay|overline|padding + |padding-box|page|painted|pan-down|pan-left|pan-right|pan-up|pan-x|pan-y|paused|petite-caps|pixelated|plaintext|pointer|portrait|pre|pre-line + |pre-wrap|preserve-3d|progress|progressive|proportional-nums|proportional-width|proximity|radial|recto|region|relative|remove|repeat|repeat-[xy] + |reset-size|reverse|revert|ridge|right|rl|rl-tb|round|row|row-resize|row-reverse|row-severse|rtl|ruby|ruby-base|ruby-base-container|ruby-text + |ruby-text-container|run-in|running|s-resize|saturation|scale-down|screen|scroll|scroll-position|se-resize|semi-condensed|semi-expanded|separate + |sesame|show|sideways|sideways-left|sideways-lr|sideways-right|sideways-rl|simplified|slashed-zero|slice|small|small-caps|small-caption|smaller + |smooth|soft-light|solid|space|space-around|space-between|space-evenly|spell-out|square|sRGB|stacked-fractions|start|static|status-bar|swap + |step-end|step-start|sticky|stretch|strict|stroke|stroke-box|style|sub|subgrid|subpixel-antialiased|subtract|super|sw-resize|symbolic|table + |table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row|table-row-group|tabular-nums|tb|tb-rl + |text|text-after-edge|text-before-edge|text-bottom|text-top|thick|thin|titling-caps|top|top-outside|touch|traditional|transparent|triangle + |ultra-condensed|ultra-expanded|under|underline|unicase|unset|upleft|uppercase|upright|use-glyph-orientation|use-script|verso|vertical + |vertical-ideographic|vertical-lr|vertical-rl|vertical-text|view-box|visible|visibleFill|visiblePainted|visibleStroke|w-resize|wait|wavy + |weight|whitespace|wider|words|wrap|wrap-reverse|x|x-large|x-small|xx-large|xx-small|y|zero|zoom-in|zoom-out + + |arabic-indic|armenian|bengali|cambodian|circle|cjk-decimal|cjk-earthly-branch|cjk-heavenly-stem|cjk-ideographic + |decimal|decimal-leading-zero|devanagari|disc|disclosure-closed|disclosure-open|ethiopic-halehame-am + |ethiopic-halehame-ti-e[rt]|ethiopic-numeric|georgian|gujarati|gurmukhi|hangul|hangul-consonant|hebrew + |hiragana|hiragana-iroha|japanese-formal|japanese-informal|kannada|katakana|katakana-iroha|khmer + |korean-hangul-formal|korean-hanja-formal|korean-hanja-informal|lao|lower-alpha|lower-armenian|lower-greek + |lower-latin|lower-roman|malayalam|mongolian|myanmar|oriya|persian|simp-chinese-formal|simp-chinese-informal + |square|tamil|telugu|thai|tibetan|trad-chinese-formal|trad-chinese-informal|upper-alpha|upper-armenian + |upper-latin|upper-roman|urdu + + |-ah-|-apple-|-atsc-|-epub-|-hp-|-khtml-|-moz-|-ms-|-o-|-rim-|-ro-|-tc-|-wap-|-webkit-|-xv- + |mso-|prince- + + |arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|system-ui|system|tahoma|times|trebuchet|ui-monospace|ui-rounded|ui-sans-serif|ui-serif|utopia|verdana|webdings|sans-serif|serif|monospace + `.replace(/\s/g, ""); + + const pseudoFunction = ` + dir + |lang + |not|has|matches|where|is + |nth-child|nth-last-child|nth-of-type|nth-last-of-type + `.replace(/\s/g, ""); + + const pseudoClasses = ` + active|any-link|checked|default|disabled|empty|enabled|first + |first-child|first-of-type|last-child|last-of-type|only-child|only-of-type|focus|focus-visible|focus-within|fullscreen|host|hover + | in-range|indeterminate|invalid|left|link|optional|out-of-range + | read-only|read-write|required|right|root|scope|target|unresolved + | valid|visited + `.replace(/\s/g, ""); + + const pseudoElements = ` + after + | before + | first-letter + | first-line + | -ah-|-apple-|-atsc-|-epub-|-hp-|-khtml-|-moz- + |-ms-|-o-|-rim-|-ro-|-tc-|-wap-|-webkit-|-xv-) + |mso-|prince- + | backdrop + | content + | grammar-error + | marker + | placeholder + | selection + | shadow + | spelling-error + `.replace(/\s/g, ""); + + const valueColorKeywordsArray = valueColorKeywords.split("|"); + const valueKeywordsArray = valueKeywords.split("|"); + const pseudoFunctionArray = pseudoFunction.split("|"); + const pseudoClassesArray = pseudoClasses.split("|"); + const pseudoElementsArray = pseudoElements.split("|"); + const allValuesArray = [...valueColorKeywordsArray, ...valueKeywordsArray]; + const allPseudoArray = [ + ...pseudoFunctionArray, + ...pseudoClassesArray, + ...pseudoElementsArray, + ]; + const testSet = new Set(allPseudoArray); + const sharedValues = allValuesArray + .filter((item) => testSet.has(item)) + .sort(); + + console.log(sharedValues); +})(); + +// LOGS: +// '-ah-', +// '-apple-', +// '-atsc-', +// '-epub-', +// '-hp-', +// '-khtml-', +// '-moz-', +// '-ms-', +// '-o-', +// '-rim-', +// '-ro-', +// '-tc-', +// '-wap-', +// '-webkit-', +// 'active', +// 'content', +// 'default', +// 'disabled', +// 'left', +// 'mso-', +// 'optional', +// 'prince-', +// 'right' + +// LOGGED VENDOR-PREFIXES: (Use as property values) +// '-ah-', +// '-apple-', +// '-atsc-', +// '-epub-', +// '-hp-', +// '-khtml-', +// '-moz-', +// '-ms-', +// '-o-', +// '-rim-', +// '-ro-', +// '-tc-', +// '-wap-', +// '-webkit-', +// 'mso-', +// 'prince-', + +// LOGGED NON-VENDOR-PREFIXES: +// Use as a property value when condensed name:value format used: +// 'left', (Such as `text-align: left;` and `float: left;`) [example selector @page :left {}] +// 'right' (Such as `text-align: right;` and `float: right;`) [example selector @page :right {}] + +// Use as a pseudo-type selector: +// 'active', (pseudo-class) [unknown use-case for a property value] +// 'disabled', (such as `button:disabled` and `input:disabled`) [unknown use-case for a property value] + +// Depends on the name tag: +// 'default', (Such as `cursor: default;` and `appearance: default;` for properties)( `input:default` and `option:default` are pseudo-classes) +// 'optional', (Such as `font-display: optional;` for a property value) ('input:optional' is a pseudo-class) + +// The :default pseudo-class is limited to form elements: +// button +// input +// option +// select + +// The :optional pseudo-class is limited to form elements: +// input +// textarea +// select + +// Unknown use-cases: +// 'content', (requires a double colon as a pseudo-element, but unknown use-case) [unknown use-case for a property value as well] diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..238964f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +.dev-assets/* +demo/* +src/spec/* diff --git a/.vscodeignore b/.vscodeignore index f6adf93..e2b2105 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,5 +1,10 @@ -.dev-assets/** -.vscode/** -.vscode-test/** +.dev-assets .gitignore -vsc-extension-quickstart.md +.package-lock.json +.prettierignore +.prettierrc +.vscode +.vscodeignore +demo +node_modules +src diff --git a/CHANGELOG.md b/CHANGELOG.md index e3962fd..b7ae9b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] - 2025-03-12 + +### Changed + +- Refactored the entire vscode-css' css.cson file that converts into the `css.tmLanguage.json` file for syntax highlighting + ## [0.4.0] - 2025-01-31 ### Added diff --git a/demo/at-container-demo.css b/demo/at-container-demo.css new file mode 100644 index 0000000..540ad01 --- /dev/null +++ b/demo/at-container-demo.css @@ -0,0 +1,141 @@ +/** + * AT CONTAINER + */ + +/* AND */ +@container (width > 400px) and (height > 400px) { +} +@container (min-width: 600px) and (orientation: landscape) { +} + +/* NOT */ +@container not (width < 400px) { +} +@container not (width < 400px) not (width > 800px) { +} +@container sidebar not (orientation: landscape) and (min-width: 600px) { +} +@container sidebar not (orientation: landscape) and (min-width: 600px) or (min-width: 600px) { +} + +/* OR */ +@container (width > 400px) or (height > 400px) {'' +} +@container (display: grid) or (display: flex) { +} + +/* CONTAINER-NAME */ +@container name (width > 400px) and (height > 400px) { +} +@container name1 name2 name3 not (width > 400px) not (height > 400px) { +} + +@container default (width > 400px) { +} +@container none default (width > 400px) { +} + +/* CONTAINER-QUERY */ +@container (min-aspect-ratio: 3 / 2) { + .video-player { + border: 5px solid #fff; + } +} + +@container (min-width: calc( var(--width) * 1rem)) { + .card { + margin: 0; + } +} + +/* STYLE-QUERY */ +@container style( --accent-color : currentColor ) { +} + +@container style(small --cards: /*comment*/ small 333px) { +} +@container card (inline-size > 30em) and style(--responsive: true) { +} +@container style( --width : calc( var(--hello) ) ) { +} + +@container style(--themeBackground), not style(--themeColor: blue), style(--themeColor: blue) and style(--themeColor: purple), style(--themeColor: blue) or style(--themeColor: purple) { +} +@container card (inline-size > 30em) and style(--responsive: true) { +} +@container style(--cards: 333px) { + article { + border: thin solid silver; + border-radius: 0.5em; + padding: 1em; + } +} + +/* NESTED @CONTAINER */ +@container summary (width > 400px) { + @container (min-width: 800px) { + .card { + margin: 50px; + } + @container (min-width: calc( var(--width) * 1rem)) { + .card { + margin: 0; + } + } + } +} +@container card (inline-size > 30em) { + @container style(--responsive: true) { + /* styles */ + } +} + +/* EVERYTHING */ +@container /*comment*/ tall /*comment*/ short not invalidName (width > 400px) and invalidName not (min-height: 400px /*comment*/ invalidName) /*comment*/ or style(--accent-color: currentColor) { + .card { + margin: 0; + .example { + margin: 0; + } + } +} + +@container name1 name2 not (max-width: calc( (768 / 16) * 1rem)) invalidName { + invalidName .wp-block-buttons { + margin: calc(var(--wp--custom--element-spacing) * 3) auto; + .wp-block-button, + .wp-element-button { + width: 100%; + } + } +} + +@container (10em <= width <= 20em) { + /* styles */ +} + +@container (inline-size >= 0px) { + /* only applies when an inline-size container is available */ + h2 { + font-size: calc(1.2em + 1cqi); + } +} + +@container (width > 40em) { + h2 { + font-size: 1.5em; + } +} + +@container my-page-layout (block-size > 12em) { + .card { + margin-block: 2em; + } +} + +@container (inline-size >= 0px) { + /* only applies when an inline-size container is available */ + h2 { + font-size: calc(1.2em + 1cqi); + } +} diff --git a/demo/at-counter-style-demo.css b/demo/at-counter-style-demo.css new file mode 100644 index 0000000..b2e0899 --- /dev/null +++ b/demo/at-counter-style-demo.css @@ -0,0 +1,3 @@ +@counter-style/*{*/winners-list/*}*/{ system: fixed; } + +@counter-style A\\01F602z {} diff --git a/demo/at-font-face-demo.css b/demo/at-font-face-demo.css new file mode 100644 index 0000000..33aa995 --- /dev/null +++ b/demo/at-font-face-demo.css @@ -0,0 +1,8 @@ +@font-face { + font-family: 'CustomFont'; + src: url('fonts/custom-font.woff2') format('woff2'), url('fonts/custom-font.woff') format('woff'); +} + +.example { + font-family: 'CustomFont', sans-serif; +} diff --git a/demo/at-font-feature-values-demo.css b/demo/at-font-feature-values-demo.css new file mode 100644 index 0000000..ef20ec9 --- /dev/null +++ b/demo/at-font-feature-values-demo.css @@ -0,0 +1,48 @@ +/* At-rule for "nice-style" in Font One */ +@font-feature-values Font One { + @styleset { + nice-style: 12; + } +} + +/* At-rule for "nice-style" in Font Two */ +@font-feature-values Font Two { + @styleset { + nice-style: 4; + } + @swash { + s\\000077a\\73hy: 1; + } +} + +/* Apply the at-rules with a single declaration */ +.example { + font-variant-alternates: styleset(nice-style); +} + +.example { + font-variant-alternates: stylistic(user-defined-ident); + font-variant-alternates: styleset(user-defined-ident); + font-variant-alternates: character-variant(user-defined-ident); + font-variant-alternates: swash(user-defined-ident); + font-variant-alternates: ornaments(user-defined-ident); + font-variant-alternates: annotation(user-defined-ident); + font-variant-alternates: swash(ident1) annotation(ident2); +} + +@font-feature-values Font name 2 { +} + +@font-feature-values Font name 2 { +} + +@font-feature-values /*{*/Font/*}*/name/*{*/2 { +} + +@font-feature-values Font name 2 { + @swash { + /* + ========*/ + swashy:/**/ 2; /**/ + } +} diff --git a/demo/at-import-demo.css b/demo/at-import-demo.css new file mode 100644 index 0000000..f3d74b6 --- /dev/null +++ b/demo/at-import-demo.css @@ -0,0 +1,22 @@ +@import url("file.css"); + +@import "file.css"; + +@import /* url("name"); */ "1.css"; + +@import/* Comment */"2.css"; + +@import"file.css"; + +@import url("1.css") print /* url(";"); */ all; + +@import "astral.css" projection; + + +/* new syntax rules for @import have no syntax highlighting */ +@import "/css/styles.css" supports(color: red); +@import url("green.css") layer supports(selector(&)) (min-width: calc(10px)); + +/* layer as a function */ +@import url("green.css") layer(bar) supports(selector(&)) + (min-width: calc(10px)); diff --git a/demo/at-keyframes-demo.css b/demo/at-keyframes-demo.css new file mode 100644 index 0000000..57fb240 --- /dev/null +++ b/demo/at-keyframes-demo.css @@ -0,0 +1,5 @@ +@keyframes Give-them-both { fROm { } To {} } + +@keyframes identifier { -50.2% } @keyframes ident2 { .25%} + +@keyframes A\\1F602Z diff --git a/demo/at-layer-demo.css b/demo/at-layer-demo.css new file mode 100644 index 0000000..da69402 --- /dev/null +++ b/demo/at-layer-demo.css @@ -0,0 +1,34 @@ +/** + * AT LAYER + */ + +/** + * Nesting Cascade Layers (`@layer`) + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#nesting_cascade_layers_layer + */ + +@layer base { + @layer support; +} + +.foo { + @layer base { + block-size: 100%; + @layer support { + & .bar { + min-block-size: 100%; + } + } + } +} +/* Equivalent without nesting */ +@layer base { + .foo { + block-size: 100%; + } +} +@layer base.support { + .foo .bar { + min-block-size: 100%; + } +} diff --git a/demo/at-media-demo.css b/demo/at-media-demo.css new file mode 100644 index 0000000..9586a90 --- /dev/null +++ b/demo/at-media-demo.css @@ -0,0 +1,82 @@ + /** + * Nesting `@media` at-rule + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#nesting_media_at-rule + */ + +/* Typical way to write Nested CSS */ +.foo { + display: grid; + @media (orientation: landscape) { + grid-auto-flow: column; + } +} +/* Expanded nested CSS as the browser parses it */ +.foo { + display: grid; + @media (orientation: landscape) { + & { + grid-auto-flow: column; + } + } +} + +/** + * Multiple nested `@media` at-rule + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#multiple_nested_media_at-rules + */ + +.foo { + display: grid; + @media (orientation: landscape) { + grid-auto-flow: column; + @media (min-width: 1024px) { + max-inline-size: 1024px;max-inline-size: 1024px;min-width: 1024px + } + } +} + +/* Equivalent without nesting */ +.foo { + display: grid; +} +@media screen and (orientation: landscape) { + .foo { + grid-auto-flow: column; + } +} +@media screen and (orientation: landscape) and (min-width: 1024px) { + .foo { + max-inline-size: 1024px; + } +} + + +@media(max-width: 37.5em) { } + +@media not print and (max-width: 37.5em){ } + +@media (max-device-width: 2px){ } + +@media (-webkit-foo: bar){ b{ } } + +@media screen and (-ms-high-contrast:black-on-white){ } + +@media (_moz-a:b){} + +@media (-hp-foo:bar){} + +@media (mso-page-size:wide){} + +h1 { }@media only screen { } h2 { } + +h1 { }@media only screen { }h2 { } + +@media/* */only/* */screen/* */and (min-width:1100px){} + +@media/*=*/(max-width:/**/37.5em)/*=*/and/*=*/(/*=*/min-height/*:*/:/*=*/1.2em/*;*/){} + +@media , {} + +@media , ,screen {} + +@media (min-aspect-ratio: 3 / 4) and (max-aspect-ratio: 20 / 17) {} diff --git a/demo/at-namespace-demo.css b/demo/at-namespace-demo.css new file mode 100644 index 0000000..2913a42 --- /dev/null +++ b/demo/at-namespace-demo.css @@ -0,0 +1,13 @@ +@namespace "XML"; + +@namespace prefix "XML" ; + +@namespace url("http://a.bc/"); + +@namespace url url("http://a.bc/"); + +@namespace/*=*/pre/*=*/"url"/*=*/; + +@namespace"XML"; + +@namespace pre\\ fix "http://url/"; diff --git a/demo/at-page-demo.css b/demo/at-page-demo.css new file mode 100644 index 0000000..b31a42a --- /dev/null +++ b/demo/at-page-demo.css @@ -0,0 +1,53 @@ +/** + * AT PAGE + */ + + @page { + content: "Page " counter(pageNumber); +} + +@page { + /* margin box at top right showing page number */ + @top-right { + content: "Page " counter(pageNumber); + } +} + +@page :first { } + +@page:right{} + +@page {} + +@page{} + +/* See: https://developer.mozilla.org/en-US/docs/Web/CSS/@page */ + +/* Targets all the pages */ +@page { + size: 8.5in 9in; + margin-top: 4in; +} + +/* Targets all even-numbered pages */ +@page :left { + margin-top: 4in; +} + +/* Targets all odd-numbered pages */ +@page :right { + size: 11in; + margin-top: 4in; +} + +/* Targets all selectors with `page: wide;` set */ +@page wide { + size: a4 landscape; +} + +@page { + /* margin box at top right showing page number */ + @top-right { + content: "Page " counter(pageNumber); + } +} diff --git a/demo/at-supports-demo.css b/demo/at-supports-demo.css new file mode 100644 index 0000000..87b5f45 --- /dev/null +++ b/demo/at-supports-demo.css @@ -0,0 +1,35 @@ +:root { + --supports-red: false; + @supports (color: red) { + --supports-red: true; + width: 100%; + row-gap: 1rem; + section { + display: flex; + row-gap: 1rem; + } + } + --x: 1; +} + +@supports (color: red) { + section { + display: flex; + row-gap: 1rem; + } +} + +/* Invalid first-level properties */ +@supports (color: red) { + --supports-red: true; + width: 100%; + row-gap: 1rem; +} + + + +@supports (font-size: 1em) { } + +@supports (--foo: green){} + +@supports (display:table-cell) or ((display:list-item) and (display:run-in)){ diff --git a/demo/function-nesting-demo.css b/demo/function-nesting-demo.css new file mode 100644 index 0000000..8c28ba0 --- /dev/null +++ b/demo/function-nesting-demo.css @@ -0,0 +1,37 @@ +/** + * FUNCTION NESTING DEMO + */ + +.example { + margin-top: calc(600 / 16 * 1rem); + + margin-bottom: calc( (768 / 16) * 1rem); + margin-bottom: calc( calc(768 / 16) * 1rem); + + margin-right: calc((768 / 16 * (768 / 16)) * 1rem); + margin-right: calc( calc(768 / 16 * calc(768 / 16) ) * 1rem); + + margin-left: calc( (768 / 16 * (768 / 16 + (768 / 16) ) ) * 1rem); + margin-left: calc( calc(768 / 16 * calc(768 / 16 + calc(768 / 16) ) ) * 1rem); + + padding-top: calc( (768 / 16 * (768 / 16 + (768 / 16 - (768 / 16)))) * 1rem); + padding-top: calc( calc(768 / 16 * calc(768 / 16 + calc(768 / 16 - calc(768 / 16) ) ) ) * 1rem); +} + +.at-media-example { + @media screen and (max-width: calc( (1280 / 16) * 1rem) ) { + max-width: calc( (768 / 16) * 1rem); + span { + max-width: calc( (600 / 16) * 1rem); + } + } +} + +@media screen and (max-width: calc( (1280 / 16) * 1rem) ) { + .at-media-example { + max-width: calc( (768 / 16) * 1rem); + span { + max-width: calc( (600 / 16) * 1rem); + } + } +} diff --git a/demo/missing-keywords.css b/demo/missing-keywords.css new file mode 100644 index 0000000..8b365a0 --- /dev/null +++ b/demo/missing-keywords.css @@ -0,0 +1,5 @@ +.missing-properties { + color: light-dark(white, black); /* light-dark() */ + margin: steps(1, end); /* steps() ?? */ + width: some-edgy-new-function(30% / 2rem + 10px); /* some-edgy-new-function() ?? */ +} diff --git a/demo/properties-demo.css b/demo/properties-demo.css new file mode 100644 index 0000000..e0ef5d7 --- /dev/null +++ b/demo/properties-demo.css @@ -0,0 +1,162 @@ +:root { + --dark-bg: #333; + --light-bg: #f9f9f9; + --primary-color: #007bff; + --var:100px +} + +.properties { + aspect-ratio: 1; + cursor: pointer; + font: 16px/1.5 'Helvetica Neue', sans-serif; + grid-column: 1/-1; + grid-row: 1 / 3; + unicode-range: u+0-7F; + unicode-range: U+0025-00FF; + custom-property: value; +} + +.properties-with-functions { + --some-custom-property-variable: calc(var(--var) * 2); + background-color: rgba(255, 0, 0, 0.5); + color: hsl(0, 100%, 50%); + color: var(--primary-color); + content: ""; + content: url('https://www.example.com'); + filter: blur(5px); + filter: brightness(1.075); + font-size: clamp(1.1rem, 0.7153rem + 1.6368vw, 1.5rem); + transform: rotate(45deg); + width: clamp(100px, calc(30% / 2rem - 10px), 900px); + width: some-edgy-new-function(30% / 2rem + 10px); +} + +.prop{ font-family:"Arial", "Courier New", monospace, sans-serif +} +.prop{ background-color:url("my image.jpg") no-repeat +} +.prop{ grid-template-columns:1fr 2fr 3fr +} +.prop{ transform:translate(10px, 20px) +} +.prop{ animation:fadeIn 3s ease-in-out infinite +} +.prop{ clip-path:polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%) +} +.prop{ box-shadow:10px 10px 5px #888888 +} +.prop{ color:#ff5733 +} +.prop{ border-radius:50% / 30% +} +.prop{ content:"hello" +} +.prop{ clip-path:circle(50% at 50% 50%) +} +.prop{ width:calc(100% - 50px) +} +.prop{ animation-timing-function:cubic-bezier(0.25, 0.1, 0.25, 1) +} +.prop{ box-shadow:10px 10px 5px red, -5px -5px 5px blue +} +.prop{ animation:fadeIn 2s, slideIn 1s +} +.prop{ background:linear-gradient(to right, red, blue) +} +.prop{ border:solid 2px black, dashed 3px red +} +.prop{ background:url("image.jpg") / cover +} +.prop{ box-shadow:10px 10px 5px rgba(0,0,0,0.5) / inset +} +.prop{ color:red !important +} +.prop{ width:calc(50% + 20px) +} +.prop{ grid-template-columns:repeat(3, 1fr + 10px) +} +.prop{ filter:contrast(100%) brightness(50% + 20%) +} +.prop{ height:calc(100vh * 0.5) +} +.prop{ animation-duration:calc(1s * 2) +} +.prop{ content:attr(data-title) "~" +} +.prop{ width:clamp(300px, 50%, 800px) +} +.prop{ grid:auto-flow dense / auto auto +} +.prop{ background-image:paint(myCustomPainter) +} +.prop{ background-image:url("image.jpg?v=2") +} +.prop{ grid-template-columns:subgrid > auto auto +} +.prop{ width:calc(var(--size) > 50px ? 50px : var(--size)) +} +.prop{ content:ATTR(VAR(--name) px, "N/A") +} +.prop{ color:HSL(0, 00100%,50%) +} +.prop{ color:HSLa(2,.0%,1%,.7) +} +.prop{ url:url(s) +} +.prop{ custom-property:url("http://example.com") +} +.prop{ content:url("http://github.com/") +} +.prop{ content:aTTr(data-width 5px, inherit) +} +.prop{ content:var(--var) +} +.prop{ max-width:calc( var(--size) * 1rem ) +} +.prop{ color:red-plus +} +.prop{ grid-row:1 / 3 +} +.prop{ property-custom:all +} +.prop{ n-_456885sstsats:astien +} +.prop{ left:left +} +.prop{ color:inherit +} +.prop{ margin:0/* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */ +} +.prop{ /* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */margin:0 +} +.prop{ float:left +} +.prop{ text-align:right +} +.prop{ appearance:default +} +.prop{ content:content +} +.prop{ max-width:calc( var(--size) * 1rem ) +} +.prop{ content:aTTr(data-width 5px, inherit) +} +.prop{ color:HSLa(2,.0%,1%,.7) +} +.prop{ width:CALC(/**==*/ ltr /**/) +} +.prop{ display:table-cell +} +.prop{ content:url("http://github.com/") +} +.prop{ width:some-edgy-new-function(30% / 2rem + 10px) +} +.prop {gap:0;margin:0 +} +.prop{ custom-property:actives +} +.prop{ \77\69\64\74\68:100% +} +.prop{ color:optional +} + diff --git a/demo/selectors-demo.css b/demo/selectors-demo.css new file mode 100644 index 0000000..f9455b6 --- /dev/null +++ b/demo/selectors-demo.css @@ -0,0 +1,2529 @@ +.nested-selectors { + :root + {gap:0} + + /* UNIVERSAL SELECTOR */ + * + {gap:0} + + + /* NESTING SELECTOR */ + & + {gap:0} + + + /* NAMESPACE SELECTOR */ + namespace|* + {gap:0} + + |div + {gap:0} + + + /* TYPE SELECTORS */ + a + {gap:0} + + pokémon-ピカチュウ + {gap:0} + + /* CUSTOM SELECTORS */ + very-custom.class, + very-custom:hover, + very-custom::shadow, + Basecamp-schedule basecamp-Schedule + {gap:0} + + halo_night + {gap:0} + + @some-weird-new-feature + {gap:0} + + /* CLASS SELECTOR */ + .class, + .étendard, + .スポンサー, + .--, + ._, + .\\33\\44-model + {gap:0} + + + /* ID SELECTOR */ + #id + {gap:0} + + #unicorn, + #洪荒之力, + #_zer0-day, + #--d3bug--, + #\\33\\44-model + {gap:0} + + + + + /* ATTRIBUTE SELECTOR */ + [attribute] + {gap:0} + + [hreflang|=fr], + [disabled], [disabled] + p + {gap:0} + + [href^="http://www.w3.org/"], + [*|title], + [marvel|origin=radiation], + [|data-hp="75"], + #div[id="0"], + .bar#div[id="0"], + .div[id="0"], + #bar.div[id], + span[ er|lang |= "%%" ], + a[name\\[0\\]="value"], + a[name\\ space|Get\\ It\\?="kek"], + span[/*]*/lang], + a[href^="#"] a[href^= "#"] a[href^="#" ], + span[class~=Java], + span[class^= 0xDEADCAFE=|~BEEFBABE ], + a[name\\[0\\]=a\\BAD\\AF\\]a\\ i] + {gap:0} + + + + /* PSEUDO-CLASS SELECTOR */ + :hover + {gap:0} + + + /* PSEUDO-ELEMENT SELECTOR */ + ::before + {gap:0} + + ::after + {gap:0} + + + /* FUNCTIONAL PSEUDO-CLASS SELECTOR */ + :is(h1, h2, h3) + {gap:0} + + :lang(zh-Hans-CN,es-419) + {gap:0} + + :lang(zh-*-CN) + {gap:0} + + *:not(.class-name):not(div) + {gap:0} + + *:not(/*(*/.class-name/*)*/):not(/*b*/) + {gap:0} + + + /* COMBINATOR SELECTORS */ + + + + /* GROUPING SELECTORS */ + + + + /* INVALID SELECTORS */ + + + + + + +} + +.tbd { + + /* Universal Selector */ + *, + *.class, + *#id, + *[attribute], + *div { + margin:0 + } + * + {gap:0} + + & + {gap:0} + + *& + {gap:0} + + + + + + /* CLASS SELECTOR*/ + + *.class + {gap:0} + + &.class + {gap:0} + + &*.class + {gap:0} + + .\33\44-model + {gap:0} + + p.class {margin:0} + div.class {margin:0} + *p.class {margin:0} + &div.class {margin:0} + *&p.class {margin:0} + + custom-selector.class {margin:0} + *custom-selector.class {margin:0} + &custom-selector.class {margin:0} + *&custom-selector.class {margin:0} + + + /* ID SELECTOR */ + #id {margin:0} + +*#id {margin:0} + +&#id {margin:0} +*&#id {margin:0} + +p#id {margin:0} +div#id {margin:0} +*p#id {margin:0} +&div#id {margin:0} +*&p#id {margin:0} + +custom-selector#id {margin:0} +*custom-selector#id {margin:0} +&custom-selector#id {margin:0} +*&custom-selector#id {margin:0} + +#id.class {margin:0} + +*#id.class {margin:0} + +&#id.class {margin:0} +*&#id.class {margin:0} + +p#id.class {margin:0} +div#id.class {margin:0} +*p#id.class {margin:0} +&div#id.class {margin:0} +*&p#id.class {margin:0} + +custom-selector#id.class {margin:0} +*custom-selector#id.class {margin:0} +&custom-selector#id.class {margin:0} +*&custom-selector#id.class {margin:0} + + #id + {gap:0} + + #\33\44-model + {gap:0} + + #c\33\44-model + {gap:0} + + a, p, div + {gap:0} + + *a + {gap:0} + + &p + {gap:0} + + *&div + {gap:0} + + [disabled], [disabled] + p + {gap:0} + + a[href^="#"] a[href^= "#"] a[href^="#" ] + {gap:0} + + 洪荒之力 + {gap:0} + + \61\ \. \@media + {gap:0} + + *custom-selector, + &custom-selector, + *&custom-selector, + custom-selector-01 + {gap:0} + + div:focus + {gap:0} + + /* ATTRIBUTE SELECTOR */ + [lang] {gap:0} /* attribute name */ + [lang="en"] /* attribute with exact value */ + [lang~="en"] {gap:0} /* attribute with space-separated values, one of which must match exactly */ + [lang|="en"] {gap:0} /* attribute with hyphen-separated values, must match the start exactly */ + [lang^="en"] {gap:0} /* attribute with value that starts with a certain string */ + [lang$="en"] {gap:0} /* attribute with value that ends with a certain string */ + [lang*="en"] {gap:0} /* attribute with value that contains a certain string */ + [lang="en" i] {gap:0} /* case-insensitive attribute with exact value */ + [lang="en" s] {gap:0} /* case-sensitive attribute with exact value */ + + a[href^="#"] {gap:0} + a[href$=".org"] {gap:0} + a[href*="example"] {gap:0} + a[href*="insensitive" i] {gap:0} + a[href*="cAsE" s] {gap:0} + a[href^="https://"][href$=".org"] {gap:0} + + [required] {gap:0} + *[required] {gap:0} + &[required] {gap:0} + *&[required] {gap:0} + input[type="text"] {max-width:0} + input[type="text"][required] {gap:0} + custom-selector[href^="https"] {gap:0} + a[class~="logo"] {gap:0} + a[href$=".org" i] {gap:0} + a[href*="example"] {gap:0} + p[required] {gap:0} + .class[href$=".pdf"] {gap:0} + #id[href*="example"] {gap:0} + + /* PSEUDO-CLASS SELECTOR - UI State */ +:focus {margin:0} +:checked {margin:0} +:disabled {margin:0} +/***/ +*:focus {margin:0} +&:focus {margin:0} +p:focus {margin:0} +div:focus {margin:0} +custom-selector:focus {margin:0} +.class:focus {margin:0} +#id:focus {margin:0} +[required]:focus {margin:0} +*[required]:focus {margin:0} +&[required]:focus {margin:0} +input[type="text"]:focus {max-width:0} +input[type="text"][required]:focus {margin:0} +custom-selector[href^="https"]:focus {margin:0} +.class[href$=".pdf"]:focus {margin:0} +#id[href*="example"]:focus {margin:0} + +p:first-child {} + + + + +.opening:first-letter {} + +q::after {} + +:-ms-input-placeholder {} + +::-webkit-input-placeholder {} + +::selection {} + +:selection + + + +/* PSEUDO-CLASS SELECTOR - Structural */ +:first-child {margin:0} +:last-child {margin:0} +:nth-child(2) {margin:0} +:nth-last-child(2) {margin:0} +:nth-of-type(odd) {margin:0} +:first-of-type {margin:0} + +*:first-child {margin:0} +&:last-child {margin:0} +p:nth-child(2) {margin:0} +div:nth-last-child(2) {margin:0} +custom-selector:nth-last-child(2) {margin:0} +.class:nth-of-type(odd) {margin:0} +#id:first-of-type {margin:0} +[required]:first-of-type {margin:0} +*[required]:first-of-typ {margin:0} +&[required]:first-of-typ {margin:0} +input[type="text"]:first-of-typ {max-width:0} +input[type="text"][required]:first-of-typ {margin:0} +custom-selector[href^="https"]:first-of-typ {margin:0} +.class[href$=".pdf"]:first-of-typ {margin:0} +#id[href*="example"]:first-of-typ {margin:0} + +:first-child +{ + margin:0 +} +:last-child +{ + margin:0 +} +:nth-child(2) +{ + margin:0 +} +:nth-last-child(2) +{ + margin:0 +} +:nth-of-type(odd) +{ + margin:0 +} +:first-of-type +{ + margin:0 +} + +*:first-child +{ + margin:0 +} +&:last-child +{ + margin:0 +} +p:nth-child(2) +{ + margin:0 +} +div:nth-last-child(2) +{ + margin:0 +} +custom-selector:nth-last-child(2) +{ + margin:0 +} +.class:nth-of-type(odd) +{ + margin:0 +} +#id:first-of-type +{ + margin:0 +} +[required]:first-of-type +{ + margin:0 +} +*[required]:first-of-typ +{ + margin:0 +} +&[required]:first-of-typ +{ + margin:0 +} +input[type="text"]:first-of-typ {max-width:0} +input[type="text"][required]:first-of-typ +{ + margin:0 +} +custom-selector[href^="https"]:first-of-typ +{ + margin:0 +} +.class[href$=".pdf"]:first-of-typ +{ + margin:0 +} +#id[href*="example"]:first-of-typ +{ + margin:0 +} + + +:where(&) { + color: red; +} + +article { + color: blue; + :where(&) { + color:red + } +} + + + + +:first-child {margin:0} +div:first-child {margin:0} +p:last-child {margin:0} +p:nth-child(2) {margin:0} +p:nth-last-child(2) {margin:0} +p:nth-of-type(odd) {margin:0} +p:first-of-type {margin:0} + + +/* PSEUDO-CLASS SELECTOR - Form */ +:valid {margin:0} +input:valid {margin:0} +input:invalid {margin:0} +input:required {margin:0} + + +/* PSEUDO-CLASS SELECTOR - Interactive State */ +:hover {margin:0} +a:hover {margin:0} +button:active {margin:0} + +/* PSEUDO-ELEMENT SELECTOR - */ +*::before {margin:0} +&::after {margin:0} +custom-selector::first-letter {margin:0} +.class::before {margin:0} +#id::after {margin:0} + + +::selection {margin:0} +p::first-letter {margin:0} +p::first-line{margin:0} +input::placeholder {margin:0} + + +/* LOGICAL SELECTOR */ +:is(h1, h2, h3) {margin:0} +:where(section, article) {margin:0} +div:not(.excluded) {margin:0} +div:has(img) {margin:0} + + + /* NAMESPACE SELECTOR */ + namespace|div {margin:0} + *|div {margin:0} + |div {margin:0} + + foo|h1 { } + +*|abbr {} + +*|* {} + +foo|* { } + +|[svg|attr=name]{} + +|h1 { } + +*| { } + +*|{ } + + + + + /* GROUPING SELECTOR */ + h1, h2, h3 {margin:0} + + + /* COMBINATORS SELECTOR */ + div p {margin:0} + div > p {margin:0} + div + p {margin:0} + div ~ p {margin:0} + + a > b + * ~ :not(.nah) + + div + div, +div+div, +div > div, +div>div, +div ~ div, +div~div { + margin: 0; +} + +/* Namespace Selector */ +|div, +namespace|div { + margin: 0; +} + + + /* NESTING SELECTOR */ + & div, + &div, + div&, + div &, + div & & &, + & div { + margin: 0; + } + + .parent { + content: U+0025; + .child-class { + content: U+00A0-00FF; + margin: 0; + } + } + + #parent-rule { + margin: 0; + .child-rule { + .test-class { + margin: 0; + h2 { + margin: 0; + } + :hover { + text-decoration: underline; + } + .test-class { + margin: 0; + :hover { + text-decoration: underline; + } + } + } + h2 { + margin: 0; + } + margin: 0; + } + } + + /** +* Nesting with the `&` nesting selector +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector +*/ + +.parent-rule { + text-decoration: none; + &:hover { + text-decoration: underline; + } +} + +/** +* Using `&` outside nested rule +* If not used in nested style rule, the & nesting selector represents the scoping root. +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule +*/ + +* { + color: blue; + font-weight: bold; +} + +&:hover { + background-color: wheat; +} + + + /** +* Creating complex selectors with CSS nesting +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors/Selectors_and_combinators#creating_complex_selectors_with_css_nesting +*/ +h2 { + & + p { + & ~ p { + font-style: italic; + width: some-edgy-new-function(30% / 2rem + 10px); + } + & + p { + color: red + } + } +} +.myClass { + & + p { + text-decoration: underline; + } +} +#myId { + & > .myClass { + outline: 3px dashed red; + } + > p #an > theirId { + font-size: 1.1rem; + } +} + + + +} + +/** + * OF :NOT + */ + +/* `of` and trailing grammar has no syntax highlighting */ +.item > div:nth-child(1 of :not(.hidden)) { + padding-left: var(--item_margin); +} +.item > div:nth-last-child(1 of :not(.hidden)) { + padding-right: var(--item_margin); +} + +/* Same as above, but using CSS Nesting formatting */ +.item { + & > div { + &:nth-child(1 of :not(.hidden)) { + padding-left: var(--item_margin); + } + &:nth-last-child(1 of :not(.hidden)) { + padding-right: var(--item_margin); + } + } +} + + + + +/* Valid Start Options */ +*div, +&div, +|div, +.class, +#id, + div, +[attribute], +:hover, +::before/* comment */, +div, +div +div, +div >div, +div ~div, +custom-selector, +/* comment */div { + margin: 0;div {margin:0} + div:focus {margin:0} +} + +section { + border:4px /*1px; + padding:1px*/ +} + +section { + margin: 0 +} + + + +/* Valid End Options */ +div&, +div.class, +div#id, +div[attribute], +div:hover, +div::before, +div+, +div>, +div~, +div , +div, +div/* comment */ { + margin: 0; +} + +/* Invalid End Options */ +div*, +div|, +div:, +div: , +div: focus, +div;, +div], +div}, +div), + +/* Invalid Start Options */ +somediv { + div: focus {margin:0} +} + +div: focus {margin:0} + + +.sooo /deep/ >>>_.>>> {} + + + +洪荒!%之力!english.^%$(_%^ + {gap:0} + + +.B!W{ + + .666{ + + .-911-{ + + .-{ + + #sort!{ + + #666{ + + #-911-{ + + #-{ + + + + + + +div { font-size: inherit; } + +div{color:inherit;float:left} + +:root { --white: #FFF; } + +a{ text-shadow: a, b; } + +#jon { color: snow; } + +p{ color: #f030; } + +a{ color: #CAFEBABE; } + +a{ color: #CAFEBABEF; } + +p { font-family: Verdana, Helvetica, sans-serif; } + +ol.myth { list-style-type: cjk-earthly-branch } + +div { font-size: 14px; } + +div { font-size: test14px; } + +div { font-size: test-14px; } + +.edge { cursor: -webkit-zoom-in; } + +.edge { width: -moz-min-content; } + +.edge { display: -ms-grid; } + +div { color: var(--primary-color) } + +a{content:aTTr(data-width px, inherit)} + +a{content:ATTR(VAR(--name) px, "N/A")} + +a{ width: calc(3px+1em); } + +a{ width: calc(3px--1em); height: calc(10-1em);} + +a{ width: calc(3px*2); } + +a{ width: calc(3px/2); } + +.foo { margin-top: calc(var(--gap) + 1px); } + +a{ color: rgb(187,255,221); } + +a{ color: RGBa( 100%, 0% ,20.17% ,.5 ); } + +a{color:HSL(0, 00100%,50%)} + +a{color:HSLa(2,.0%,1%,.7)} + +a{ color: RGBA(var(--red), 0% , 20%, .2)} + +a{ color: rgba(/**/255/*=*/,0,/*2.2%*/51/*,*/0.2)} + +a{ background-image: linear-gradient( 45deg, blue, red ); } + +a{ background-image: LINear-graDIEnt( ellipse to left top, blue, red); + +a{ background-image: radial-gradient(farthest-corner at 45px 45px , #f00 0%, #00f 100%);} + +a{ background-image: RADial-gradiENT(16px at 60px 50%,#000 0%, #000 14px, rgba(0,0,0,.3) 18px, transparent 19px)} + +a{ zoom: cubic-bezier(/**/1.2,/*=*/0,0,0/**/)} + +a{ before: steps(0, start); after: steps(1, end); } + +a{color: var(--name)} + +a{color: var( --name )} + +a{ color: var( /*=*/ --something ) } + +.bar{ width: var(--page-width, /*;;;);*/ 2); } + +a{ content: attr (title); } + +a{url:url (s)} + +a{content:url ("http://github.com/");} + +a{content: url (http://a.pl/)} + +a{ color: rgb (187,255,221); } + +a{ unicode-range: U+A5 } + +a{ unicode-range: U+0025-00FF } + +a{ unicode-range: u+0-7F } + +a{ unicode-range: U+4?? } + +a{ unicode-range: U+0025-00FF, U+4?? } + +very-custom { content: "\\c0ffee" } + +\\61 \\{ { } \\} + +\\61\\ \\. \\@media {} + +a { \\77\\69\\64\\74\\68: 20px; } + +a { content: \\1F764; } + +a{ content: "aaaa + +a{ content: "aaa\\"aa + +@import /* url("name"); */ "1.css"; + +@import/*";"*/ url("2.css"); + +@import url("3.css") print /* url(";"); */; + +@font-face/*"{;}"*/{} + +/* comment */ @media + +@media/* comment */ () + +@media (max-height: 40em/* comment */) + +section {border:4px/*padding:1px*/} + + +/* disable-prettier */ + +/* 'source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css' */ + +[disabled]:not(:first-child)::before:hover\n ~ div.object\n + #id.thing:hover > strong ~ p::before,\na::last-of-type,/*Comment*/::selection > html[lang^=en-AU],\n*>em.i.ly[data-name|=\"Life\"] { } + +foo|h1 { } + +*|abbr {} + +*|* {} + +foo|* { } + +|[svg|attr=name]{} + +|h1 { } + +*| { } + +h1 { }@media only screen { } h2 { } + +h1 { }@media only screen { }h2 { } + +@font-feature-values Font name 2 { } + +@font-feature-values Font name 2 {\n@swash{/*\n========*/swashy:/**/2;/**/}\n} + +@page :first { } + +@page {} + +@page{} + +::selection, +:selection, +:selection +{gap:0} + +@viewport { min-width: 640px; max-width: 800px; } + +@-O-VIEWPORT\n{\n zoom: 0.75;\n min-zoom: 0.5;\n max-zoom: 0.9;\n} + +@-ms-viewport/*{*/{/*\n==*/orientation: landscape;\n} + +p{ left:left } + +div { font-size: inherit; } + +div{color:inherit;float:left} + +very-custom { color: inherit }\nanother-one { display : none ; } + +p{ color: #f030; } + +a{ color: #CAFEBABE; } + +a{content:aTTr(data-width px, inherit)} + +.foo { margin-top: calc(var(--gap) + 1px); } + +.grad {\n background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,\n from( rgb(0, 171, 235)),\n color-stop(0.5, rgb(255, 255, 255)),\n color-stop(0.5, rgb(102, 204, 0)),\n to(rgb(255, 255, 255))),\n -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)),\n -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)),\n -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)),\n -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700));\n} + +a{\n background-image: image-set( \"foo.png\" 1x,\n \"foo-2x.png\" 2x,\n \"foo-print.png\" 600dpi );\n} + +a{color: var(--name)} + +a{ content: attr (title); } + +a{url:url (s)} + +a{content:url ("http://github.com/");} + +a{content: url (http://a.pl/)} + +\\61 \\{ { } \\} + +a { content: \\1F764; } + +a{\n content: \"aaa\\\"aa\\\naaaa\naaaa; color: red;\n} + +@font-face/*"{;}"*/{} + +section {border:4px/*padding:1px*/} + + section {\n border:4px /*1px;\n padding:1px*/\n} + +a { place-items: center center; } + +a { place-self: center center; } + +a { place-content: center center; } + +a { row-gap: 5px; } + +a{ before: steps(0, start); after: steps(1, end); } + +very-custom { color: inherit } +another-one { display : none ; } + +html { + --var : 'hello' +} + +div custom-element:focus { + + div custom-element:focus { + margin: 0; + row-gap: 0; + } + + div custom-element::before { + margin: 0; + row-gap: 0; + } + + div:focus { + margin: 0; + margin:: 0; + } + + custom-element as-tffs:focus st-ass { + max-width: 0; + } + + div custom-element:focus > custom-element { + margin: 0; + } + + custom-element:focus { + max-width: 0; + + h2 { + margin:auto; + } + } + + margin: 0; + + + + a{ before: steps(0, start); after: steps(1, end); } + + +} + +a > b + * ~ :not(.nah) {gap:0} + +[disabled]:not(:first-child)::before:hover +~ div.object ++ #id.thing:hover > strong ~ p::before, +a::last-of-type,/*Comment*/::selection > html[lang^=en-AU], +*>em.i.ly[data-name|=\"Life\"] {gap:0} + +a:dir(ltr ){ + max-width:calc(1*44px) +} +*:dir( rtl){max-width:0 +} + +/* 61 . @media {} */ + +a{ content: attr(title); } /* p = 'source.css', 'meta.property-list.css', 'meta.property-name.css' */ + +p { left:left } /* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */ + +:-ms-input-placeholder {} + +/* ROOT */ +div,div,div:focus, +div::after, +custom-selector, +custom-selector:focus :focus, +custom-selector::after, +custom-selector :focus, +custom-selector ::after, +div custom-selector, +div /* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */custom-selector:focus, +div custom-selector::after, +div custom-selector + div custom-selector, +div custom-selector + div custom-selector, +div custom-selector:focus ~ div custom-selector:focus, +div custom-selector:focus ~ div custom-selector:focus, +div custom-selector::after > div custom-selector::after, +div custom-selector::after > div custom-selector::after, +custom-selector#id, +custom-selector #id, +custom-selector.class, +custom-selector .class, +custom-selector another-custom-selector very-custom-selector, +custom-selector > another-custom-selector + very-custom-selector, +custom-selector>another-custom-selector+very-custom-selector, +start-start:where(#id) nth-child-class:nth-child(2) custom-selector:not(.class), +h2, +h4, +span, +header, +nav, +label, +content { + + div { + margin:0; + property-custom:all + } + + --var: "value"; + --var:"value"; + content: var(--var); + content:var(--var); + margin: 0; + margin:0; + max-width: 0; + max-width:0; + property: all; + property:all; + p: all; + p:all; + property-custom: all !important; + + div { + div { + margin: 0; + + .sooo /deep/ >>>_.>>>_ { + margin: 0 + } + } + } + + property-custom: all !important; + + div:focus + { + div:focus + { + margin: 0 + } + /* content: var(--var); */ + } + + + div { + margin: 0 + } + div + { + margin: 0 + } + + /* NESTED */ + div, + div:focus, + div::after, + custom-selector, + custom-selector:focus, + custom-selector::after, + custom-selector :focus, + custom-selector ::after, + div custom-selector, + div custom-selector:focus, + div custom-selector::after, + div custom-selector + div custom-selector, + div custom-selector:focus ~ div custom-selector:focus, + div custom-selector::after > div custom-selector::after custom-selector#id, + custom-selector.class, + custom-selector another-custom-selector very-custom-selector, + custom-selector > another-custom-selector + very-custom-selector, + start-start:where(#id) nth-child-class:nth-child(2) custom-selector:not(.class) h2, + h4, + span, + header, + nav, + label, + content { + + .sooo /deep/ >>>_.>>>_ { + margin: 0; + } + + + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all !important; + property-custom: all !important; + p: focus; + } + + div:focus { + margin: 0; + } + + div:focus { + margin: 0; + } + + div:focus { + margin: 0; + } + + div:focus { + div:focus { + p: all; + margin:0 + + div:focus { + p: all; + margin:0 + + div:focus { + p: all; + margin:0 + } + } + } + } + + div:focus { + margin: 0; + } + + div:focus { + margin: 0; + } + + /* With selector end bracket */ + custom-selector { + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all; + property-custom:all + } + h2, + h4, + span, + header, + nav, + label, + content { + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all; + property-custom:all + } + + /* Without selector end bracket */ + custom-selector { + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all; + property-custom: all; + } + h2, + h4, + span, + header, + nav, + label, + content { + margin: 0; + property: all; + a: auto; + property-custom: all; + } + + /* With end selector bracket + no last prop terminator */ + custom-selector:focus + { + margin: 0; + max-width: 0; + property: all; + p: auto; + property-custom: all; + } + h2, + h4, + span, + header, + nav, + label, + content { + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all; + property-custom:all; + select:first-child + { + + } + } + + /* Without selector end bracket + no last prop terminator */ + custom-selector::after, + custom-selector:not(.class) + { + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all; + property-custom: all; + } + h2, + h4, + span, + header, + nav, + label, + content { + --var: "value"; + --var: "value"; + content: var(--var); + content: var(--var); + margin: 0; + margin: 0; + max-width: 0; + max-width: 0; + property: all; + property: all; + p: all; + p: all; + property-custom: all; + property-custom: all; + + /* Properties with inner comments */ + margin: /* comment */ 0; + /* comment */ + margin: 0; + /* comment */ + margin: /* comment */ 0 /* comment */; /* comment */ + max-width: /* comment */ 0; +} + +div { + property-custom: all; + + + /* PSEUDO ELEMENTS */ + select, + select:focus, + select::after, + select :focus, + select ::after { + margin: 0; + } + + property-custom: all; + + custom-selector:after, + custom-selector::focus, + custom-selector:focus { + margin: 0; + property-custom: all; + } + + :where(&) { + color: red; + } + + property-custom:all +} + +/* INVALID */ +custom-selector::::focus, +select:::::after { + margin: 0; +} + +/* pseudo-element not touching preceding colon */ +custom-selector: not(.class) { + margin: 0; +} + +/* Custom selector contains uppercase letters */ +custom-selectSTARTor { + margin: 0; +} + +/* Custom selector or property starts with a digit */ +9-custom-selector { + margin: 0; + 9custom-property: value; +} + + +/** +* CSS NESTING DEMO +*/ + +/** +* Nesting without the `&` nesting selector +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector +*/ + +.parent-rule { + margin: 0; + .child-rule { + margin: 0; + } +} + +.parent-rule { + text-decoration: none; + :hover { + text-decoration: underline; + } +} + +#id~.example {gap:0} + +/** +* Nesting with the `&` nesting selector +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector +*/ + +.parent-rule { + text-decoration: none; + &:hover { + text-decoration: underline; + } +} + +.example { + font-family: system-ui; + font-size: 1.2rem; + & > a { + color: tomato; + &:hover, + &:focus { + color: ivory; + background-color: tomato; + } + } +} + +/** + * Appending the `&` nesting selector + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#appending_the_nesting_selector + */ + +.card { + /* .card styles */ + margin: 0; + .featured & { + /* .featured .card styles */ + margin: 0; + } +} + +.card { + /* .card styles */ + margin: 0; + .featured & & & { + /* .featured .card .card .card styles */ + margin: 0; + } +} + +.card { + padding: 0.5rem; + border: 1px solid black; + border-radius: 0.5rem; + & h2 { + /* equivalent to `.card h2` */ + color: slateblue; + .featured & { + /* equivalent to `:is(.card h2):is(.featured h2)` */ + color: tomato; + } + } +} + +/** + * Appending the `&` nesting selector as a compound selector + */ + +.example { + section& { + /* equivalent to `section.example` */ + margin: 0; + } + .class& { + /* equivalent to `.class.example` */ + padding: 0; + } + #id& { + /* equivalent to `#id.example` */ + border: 1px solid black; + } + @media screen and (max-width: 1100px) { + section& { + margin: 0; + } + .class& { + padding: 0; + } + #id& { + margin: 0; + } + } +} + +/** +* Using `&` outside nested rule +* If not used in nested style rule, the & nesting selector represents the scoping root. +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule +*/ + +& { + color: blue; + font-weight: bold; +} + +&:hover { + background-color: wheat; +} + +/** + * Nesting `@media` at-rule + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#nesting_media_at-rule + */ + +/* Typical way to write Nested CSS */ +.foo { + display: grid; + @media (orientation: landscape) { + grid-auto-flow: column; + } +} +/* Expanded nested CSS as the browser parses it */ +.foo { + display: grid; + @media (orientation: landscape) { + & { + grid-auto-flow: column; + } + } +} + +/** + * Multiple nested `@media` at-rule + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#multiple_nested_media_at-rules + */ + +.foo { + display: grid; + @media (orientation: landscape) { + grid-auto-flow: column; + @media (min-width: 1024px) { + max-inline-size: 1024px; + } + } +} + +/* Equivalent without nesting */ +.foo { + display: grid; +} +@media (orientation: landscape) { + .foo { + grid-auto-flow: column; + } +} +@media (orientation: landscape) and (min-width: 1024px) { + .foo { + max-inline-size: 1024px; + } +} + +/** + * Nesting Cascade Layers (`@layer`) + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#nesting_cascade_layers_layer + */ + +@layer base { + @layer support; +} + +.foo { + @layer base { + block-size: 100%; + @layer support { + & .bar { + min-block-size: 100%; + } + } + } +} +/* Equivalent without nesting */ +@layer base { + .foo { + block-size: 100%; + } +} +@layer base.support { + .foo .bar { + min-block-size: 100%; + } +} + +/** +* Creating complex selectors with CSS nesting +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors/Selectors_and_combinators#creating_complex_selectors_with_css_nesting +*/ +h2 { + & + p { + & ~ p { + font-style: italic; + } + & + p { + color: red; + } + } +} +.myClass { + & + p { + text-decoration: underline; + } +} +#myId { + & > .myClass { + outline: 3px dashed red; + } + > p #anotherId { + font-size: 1.1rem; + } +} + +/** +* Nesting and compound selectors +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#compound_selectors +*/ + +.notice { + color: black; + padding: 1rem; + .notice-heading:before { + /* equivalent to `.notice .notice-heading:before` */ + content: "ℹ︎ "; + } + &.warning { + /* equivalent to `.notice.warning` */ + background-color: #d81b60; + border-color: #d81b60; + color: white; + .warning-heading:before { + /* equivalent to `.notice.warning .warning-heading:before` */ + content: "! "; + } + } + &.success { + /* equivalent to `.notice.success` */ + background-color: #004d40; + border-color: #004d40; + color: white; + .success-heading:before { + /* equivalent to `.notice.success .success-heading:before` */ + content: "✓ "; + } + } +} + +.notice { + color: black; + padding: 1rem; + .notice-heading:before { + /* equivalent to `.notice .notice-heading:before` */ + content: "ℹ︎ "; + } + .warning { + /* equivalent to `.notice.warning` */ + background-color: #d81b60; + border-color: #d81b60; + color: white; + .warning-heading:before { + /* equivalent to `.notice.warning .warning-heading:before` */ + content: "! "; + } + } + .success { + /* equivalent to `.notice.success` */ + background-color: #004d40; + border-color: #004d40; + color: white; + .success-heading:before { + /* equivalent to `.notice.success .success-heading:before` */ + content: "✓ "; + } + } +} + +@supports not ((tab-size:4) or (-moz-tab-size:4)){ + body::before{ + content: \"Come on, Microsoft (Get it together already)…\"; + + \\61\\ \\. \\@media {} + +/** +* CSS NESTING MODULES +* For reviewing CSS nesting and non-nesting syntax For any issues. +*/ + +/** +* At Rules +*/ + +@container (width > 400px) { + h2 { + font-size: 1.5em; + } + .card { + width: 50%; + background-color: gray; + font-size: 1em; + } +} +/* with an optional */ +@container tall (height > 30rem) { + h2 { + line-height: 1.6; + } +} +.post { + container-name: tall; + container-type: inline-size; +} +/* shorthand syntax */ +.post { + container: tall / inline-size; +} + +@counter-style thumbs { + system: cyclic; + symbols: "\1F44D"; + suffix: " "; +} +ul { + list-style: thumbs; +} + +@font-face { + font-family: "MyWebFont"; + src: url("webfont.woff2") format("woff2"), url("webfont.woff") format("woff"); +} + +@font-feature-values MyFont { + @styleset { + nice-style: 1; + } + @stylistic { + nice-style: 1; + } + @character-variant { + nice-style: 1; + } + @swash { + nice-style: 1; + } + @ornaments { + nice-style: 1; + } + @annotation { + nice-style: 1; + } +} + +@import url("https://fonts.googleapis.com/css?family=Open+Sans"); + +@keyframes slidein { + from { + margin-left: 100%; + width: 300%; + } + to { + margin-left: 0%; + width: 100%; + } +} + +@layer base { + h1 { + color: red; + } +} + +@media (max-width: 600px) { + .facet_sidebar { + display: none; + } +} +@media (min-width: 600px), (orientation: portrait) { + /* Styles */ +} + +@page { + size: A4; + margin: 10%; +} +@page :left { + margin-top: 4in; +} +@page wide { + size: a4 landscape; +} +@page { + /* margin box at top right showing page number */ + @top-right { + content: "Page " counter(pageNumber); + } +} + +/* @property is not cross-browser friendly yet */ +/* @property --my-color { + syntax: ''; + inherits: false; + initial-value: black; +} */ + +/* @scope is not cross-browser friendly yet */ +/* @scope (.article-body) to (figure) { + img { + border: 5px solid black; + background-color: goldenrod; + } + :scope { + background: rebeccapurple; + color: antiquewhite; + font-family: sans-serif; + } +} */ + +@supports (display: grid) { + .grid { + display: grid; + } +} + +/** +* FEATURE QUERY OPERATORS (AND, OR, NOT) +*/ + +/* AND */ +@container (width > 400px) and (height > 400px) { +} +@media (min-width: 600px) and (orientation: landscape) { +} +@supports (display: grid) and (gap: 1rem) { +} + +/* NOT */ +@container not (width < 400px) { +} +@media not (orientation: landscape) { +} +@supports not (display: flex) { +} + +/* OR */ +@container (width > 400px) or (height > 400px) { +} +@supports (display: grid) or (display: flex) { +} + +/** +* COMBINATORS +*/ + +.example > li { + margin: 0; +} +div > span { + margin: 0; +} +p ~ span { + color: red; +} +img + p { + color: red; +} + +.example { + div { + > span { + margin: 0; + } + } + p { + ~ span { + color: red; + } + } + img { + + p { + color: red; + } + } +} + +.example { + & div { + & > span { + margin: 0; + } + } + & p { + & ~ span { + color: red; + } + } + & img { + & + p { + color: red; + } + } +} + +/** +* COMMAS +*/ + +html, +body { + margin: 0; + &.a, + &.b { + margin: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + html, + html:focus-within { + scroll-behavior: auto; + &.a, + &.b { + margin: 0; + } + } +} + +.class, +.example { + @media (min-width: 500px) { + &::before, + &::after { + content: '"'; + display: block; + } + } +} + +.class, +.example { + fieldset, + span { + max-width: calc((768 / 16) * 1rem); + &.class_left, + &.class_right { + flex: 0 0 100%; + } + } +} + +@media only screen and (max-width: calc( (768 / 16) * 1rem )) { + .class, + .example { + fieldset, + span { + max-width: calc((768 / 16) * 1rem); + flex: 0 0 100%; + &.class_left, + &.class_right { + flex: 0 0 100%; + } + } + } +} + +.class, +.example { + @media only screen and (max-width: calc( (768 / 16) * 1rem )) { + fieldset, + span { + max-width: calc((768 / 16) * 1rem); + flex: 0 0 100%; + &.class_left, + &.class_right { + flex: 0 0 100%; + } + } + } +} + +/** +* FUNCTIONS +*/ + +.example { + color: rgb(255, 0, 0); + background-color: oklch(70% 0.1 315); + margin-top: calc((100 / 16) * 1rem); + margin-bottom: calc(var(--size, 10px) * 1rem); + max-width: min(100%, 600px); + padding: clamp(1rem, 5%, 2rem); + width: calc(100% - 3em); +} + + + +/** + * PSEUDO CLASS NESTING + */ + +.class { + > :nth-child(1) { + margin: 0; + } + @media screen and (max-width: 1100px) { + > :nth-child(1) { + margin: 0; + > :nth-child(1) { + margin: 0; + } + } + } +} + +/* REFERENCES FOR ADDING ADDITIONAL TYPES */ +/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors */ +/* https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes */ +/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_pseudo-elements */ +/* https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements */ + +/* +- selectors +- tag-names +- combinators +- functional-pseudo-classes +- pseudo-classes +- pseudo-elements +- urls +- color-keywords +- functions +- timing-function +- keyword.operator.shape +- property-names +- property-values +- property-keywords +- string +*/ + +* > .example { + gap: 0; +} + + +.example { + font-family: system-ui; + font-size: 1.2rem; + a { + color: tomato; + max-width:calc(var(--size) * 1rem) + } + section { + display: flex; + row-gap:1rem +} + +:where(&) { + color: red; +} + +article { + color: blue; + :where(&) { + color:red + } +} + +/** +* Nesting with the `&` nesting selector +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector +*/ + +.parent-rule { + text-decoration: none; + &:hover { + text-decoration: underline; + } +} + +/** +* Using `&` outside nested rule +* If not used in nested style rule, the & nesting selector represents the scoping root. +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule +*/ + +* { + color: blue; + font-weight: bold; +} + +&:hover { + background-color: wheat; +} + +/** + * Appending the `&` nesting selector as a compound selector + */ + +section& { + /* equivalent to `section.example` */ + margin: 0; +} + +.example { + section& { + /* equivalent to `section.example` */ + margin: 0; + } + .class ~ .class& { + margin: 0; + } + .class& { + /* equivalent to `.class.example` */ + padding: 0; + } + #id& { + /* equivalent to `#id.example` */ + border: 1px solid black; + } + @media screen and (max-width: 1100px) { + section& { + margin: 0; + } + .class& { + padding: 0; + } + #id& { + margin: 0; + } + } +} + +/** + * Appending the `&` nesting selector + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#appending_the_nesting_selector + */ + +.card { + /* .card styles */ + margin: 0; + .featured & { + /* .featured .card styles */ + margin: 0; + } +} + +.card { + /* .card styles */ + margin: 0; + .featured & & & { + /* .featured .card .card .card styles */ + margin: 0; + } +} + +.card { + padding: 0.5rem; + border: 1px solid black; + border-radius: 0.5rem; + & h2 { + /* equivalent to `.card h2` */ + color: slateblue; + .featured & { + /* equivalent to `:is(.card h2):is(.featured h2)` */ + color:tomato + } + } +} + +/** + * INVALID SELECTORS + */ + + ,selector-cannot-start-with-comma + {gap:0} + + 1-selector-cannot-start-with-number + {gap:0} + + -selector-cannot-start-with-single-hyphen + {gap:0} + + #6-id-cannot-start-with-number + {gap:0} + + .6-class-cannot-start-with-number + {gap:0} + + + very-custom::shadow {gap:0} + q::after {gap:0} + + [href^="http://www.w3.org/"] + /* {gap:0} */ + { :CALC(/**==*/ltr/**/) } + + :something {gap:0} + + :DIR(/**==*/ltr/**/) + {gap:0} + + :DIR(/** + ==*/ltr/* + */) + {gap:0} + + /** + * Concatenation (is not possible) + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#concatenation_is_not_possible + */ + + .component { + &__child-element { + gap: 0; + } + } + /* In Sass this becomes the CSS below, but CSS nesting doesn't allow this */ + .component__child-element { + gap: 0; + } + + /** + * Invalid nested style rules + * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#invalid_nested_style_rules + */ + .parent { + >>> .child-element { + gap: 0; + } + + /* .parent styles these work fine */ + & %invalid { + /* %invalid styles all of which are ignored */ + gap: 0; + } + & .valid { + /* .parent .valid styles these work fine */ + gap: 0; + + } + } + + + + +:hover, +.a { + div > p, ul + li, .class, #id + {gap:0} + + a:active, + a:any-link, + a:checked, + a + { + float:left + } + a + { + text-align:right + } + + color:default {gap:0} + + input:default + { + color:default + } + button:disabled + { + color:disabled + } + + input:default + + { + appearance:default + } + option:default + { + appearance:default + } + + input::content + { + content:content + } + [disabled]:hover, + [disabled]:not(:first-child)::before:hover + ~ div.object + + #id.thing:hover > strong ~ p::before, + + #id.thing:hover > strong ~ p::before + a::last-of-type,/*Comment*/::selection > html[lang^=en-AU], + *>em.i.ly[data-name|=\"Life\"] + {gap:0} + + DIV:HOVER + {gap:0} + #id::BefORE + {gap:0} + #id::aFTEr + {gap:0} + TABle:nTH-cHILD(2N+1), + table:nth-child(2N+1) + {gap:0} + htML:NOT(.htiml) + {gap:0} + I::BACKDROP + {gap:0} + I::-mOZ-thing + {gap:0} + very-custom::shadow + {gap:0} + q::after + {gap:0} + I::BACKDROP + {gap:0} + + a:active b:focus + {margin:0 + } + a:active b:focus + {margin:0 + } + a:active& + {margin:0 + } + a:active + {margin:0 + } + a:active:focus + {margin:0 + } + a:active~:focus + {margin:0 + } + + a:actives { margin:0 + } + a:actives b:focus { + margin:0 + } + a:actives b:focus { + margin:0 + } + a:actives b:focus + {margin:0 + } + + a:actives + {margin:0 + } + + +div:focus { + a:dir(ltr ) + {gap:0} + + a:dir(rtl) + {gap:0} + + a:lang() + {gap:0} + + a:first-child + {gap:0} + + .after-prop {gap:0;max-width:calc( var(--size) * 1rem ); + } + .open{ max-width:calc( var(--size) * 1rem ) } + .close{ gap:0 } max-width:calc( var(--size) * 1rem ); + p{ content:aTTr(data-width 5px, inherit) } + + p:dir(ltr ){gap:0; max-width:calc(1*44px);} + + h2 {margin:auto} + + *:dir( rtl) + {gap:0} + + p {margin:0;p:all} + + div custom-element:focus > custom-element + {gap:0} + + custom-element:focus as-tffsfocus st-ass + {gap:0} + + #nested-selectors:focus, + custom-selector:focus, focus, + div /* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */custom-selector:focus, + start-start:where(#id) nth-child-class:nth-child(2) custom-selector:not(.class), + custom-selector:focus, + start-start:where(#id) nth-child-class:nth-child(2) custom-selector:not(.class) h2, + select:focus, + article { + gap:0; + } + + :where(&) { + max-width:0; + } + + div:where(&) + { + margin:0; div{margin:0} + color:red; + } + + p{ left:left } + + p{color:inherit;float:left} + + p{content:aTTr(data-width px, inherit)} + + p{content:ATTR(VAR(--name) px, "N/A")} + + p{color:HSL(0, 00100%,50%)} + + p{color:HSLa(2,.0%,1%,.7)} + + p{ url:url(s) } + + p{content:url("http://github.com/");} + + p{content:aTTr(data-width px, inherit)} + + @supports (display:table-cell) or ((display:list-item) and (display:run-in)) + {} + + p { + \77\69\64\74\68: 100%; + } + +} + +@supports (display:table-cell) or ((display:list-item) and (display:run-in)) +{} + +@page:right +{} + +.invalid { + width: CALC(/**==*/ ltr /**/); +} diff --git a/demo/shared-names-demo.css b/demo/shared-names-demo.css new file mode 100644 index 0000000..3e3614d --- /dev/null +++ b/demo/shared-names-demo.css @@ -0,0 +1,121 @@ +/* + * SHARED NAMES + * Names that are shared between selectors and properties. + */ + +.shared-names { + /* HTML Selectors */ + + content + { + content:url('https://www.example.com') + } + font + { + font:16px/1.5 'Helvetica Neue', sans-serif + } + header + { + header:initial + } + image + { + image:initial + } + label + { + label:initial + } + mark + { + mark:initial + } + marquee + { + marquee:initial + } + mask + { + mask:initial + } + nav + { + nav:initial + } + ruby + { + ruby:initial + } + shadow + { + shadow:initial + } + span + { + span: initial + } + style + { + style:initial + } + + /* SVG Selectors */ + color-profile + { + color-profile:initial + } + cursor + { + cursor:initial + } + filter + { + filter:initial + } + line + { + line:initial + } + text + { + text:initial + } +} + +content, +cursor, +filter, +span { + content: ""; + cursor: pointer; + filter: brightness(1.075); +} + +.example { + &::before { + content: ""; + cursor: pointer; + filter: brightness(1.075); + } + & content, + & cursor, + & filter, + & span { + content: ""; + cursor: pointer; + filter: brightness(1.075); + } +} + +.example { + span, + header, + nav, + label, + ruby, + mark, + svg { + gap:0 + } +} + diff --git a/demo/shared-values-demo.css b/demo/shared-values-demo.css new file mode 100644 index 0000000..405829a --- /dev/null +++ b/demo/shared-values-demo.css @@ -0,0 +1,128 @@ +/* Shared values between selectors and properties. Only matters when using condense formatting without any closing punctuation such as `name:value`, otherwise follow punctuation rules */ + +@supports/*===*/not/*==****************| +==*/(display:table-cell)/*============*/ and (display: list-item)/*}*/{} + +select :content, +select:content, +select:not(something):content +{ + color:content +} + +/* ACTIVE */ +.nested-example { + div:active {gap:0} + + /* Unknown use case for active as a property value so it's always a selector */ + select :active, + select:active + { + color:active + + .nested-example { + div:active {gap:0} + div { + content: '{' + } + + select :active, + /* Unknown use case for active as a property value so it's always a selector */ + select:active + { + color:active + } + } + } +} + +.nested-example +{ + --var:value +} + +/* CONTENT */ +.nested-example +{ + /* div:content {gap:0} */ + + select :content, + /* Unknown use case for content as a pseudo-class selector or a property value so it's always a property value by default */ + select:content + { + color:content + }select + {gap:0} +} + +/* DEFAULT */ +.nested-example +{ + div:default {gap:0} + + button :default, + /* The :default pseudo-class is limited to the following form elements: */ + button:default, + input:default, + option:default, + select:default, + other-selectors-not-included:default + { + appearance:default + } +} + +/* DISABLED */ +.nested-example { + div:disabled {gap:0} + + button :disabled, + /* Unknown use case for disabled as a property value so it's always a selector */ + button:disabled, + input:disabled + tsstst: hello + { + color:disabled +} + +/* OPTIONAL */ +.nested-example { + div:optional {gap:0} + + input :optional, + /* The :optional pseudo-class is limited to the following form elements: */ + input:optional, + select:optional, + textarea:optional, + other-selectors-not-included:optional + { + color:optional + } +} + +/* LEFT */ +.nested-example { + div:left {gap:0} + + @page :left + {} + + select :left, + /* :left is always a property value in condensed format */ + select:left + { + float:left + } +} + +/* RIGHT */ +.nested-example { + @page :right {} /* Not used in condensed format */ + + select :right, + /* :right is always a property value in condensed format */ + select:right + { + float:right + } +} diff --git a/demo/unterminated-identifiers.css b/demo/unterminated-identifiers.css new file mode 100644 index 0000000..67bfa55 --- /dev/null +++ b/demo/unterminated-identifiers.css @@ -0,0 +1,233 @@ +.prop{ --some-custom-property-variable: calc(var(--var) * 2) +} +.prop{ \77\69\64\74\68:100% +} +.prop{ color:disabled +} +.prop{ color:optional +} +.prop{ cursor:anything +} +.prop{ custom-property:hello +} +.prop{ width:-moz-min-content +} +.prop{ span:hello +} +.prop{ animation-duration:calc(1s * 2) +} +.prop{ animation-timing-function:cubic-bezier(0.25, 0.1, 0.25, 1) +} +.prop{ animation:fadeIn 2s, slideIn 1s +} +.prop{ animation:fadeIn 3s ease-in-out infinite +} +.prop{ appearance:default +} +.prop{ aspect-ratio:1 +} +.prop{ background-color:#f00 +} +.prop{ background-color:rgba(255, 0, 0, 0.5) +} +.prop{ background-color:url("my image.jpg") no-repeat +} +.prop{ background-color:wheat +} +.prop{ background-image:linear-gradient( 45deg, blue, red ) +} +.prop{ background-image:paint(myCustomPainter) +} +.prop{ background-image:RADial-gradiENT(16px at 60px 50%,#000 0%, #000 14px, rgba(0,0,0,.3) 18px, transparent 19px) +} +.prop{ background-image:url("image.jpg?v=2") +} +.prop{ background:linear-gradient(to right, red, blue) +} +.prop{ background:url("image.jpg") / cover +} +.prop{ border-radius:50% / 30% +} +.prop{ border:solid 2px black, dashed 3px red +} +.prop{ box-shadow:10px 10px 5px #888888 +} +.prop{ box-shadow:10px 10px 5px red, -5px -5px 5px blue +} +.prop{ box-shadow:10px 10px 5px rgba(0,0,0,0.5) / inset +} +.prop{ clip-path:circle(50% at 50% 50%) +} +.prop{ clip-path:polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%) +} +.prop{ color:#ff5733 +} +.prop{ color:currentColor +} +.prop{ color:default +} +.prop{ color:hsl(0, 100%, 50%) +} +.prop{ color:HSLa(2,.0%,1%,.7) +} +.prop{ color:inherit +} +.prop{ color:red !important +} +.prop{ color:red-plus +} +.prop{ color:var(--primary-color) +} +.prop{ span:"" +} +.prop{ content:"" +} +.prop{ content:"hello" +} +.prop{ content:attr(data-title) "~" +} +.prop{ content:aTTr(data-width 5px, inherit) +} +.prop{ content:aTTr(data-width 5px, inherit) +} +.prop{ content:ATTR(VAR(--name) px, "N/A") +} +.prop{ content:content +} +.prop{ content:U+0025 +} +.prop{ content:U+00A0-00FF +} +.prop{ content:url('https://www.example.com') +} +.prop{ content:var(--var) +} +.prop{ cursor:pointer +} +.prop{ custom-property:url("http://example.com") +} +.prop{ custom-property:value +} +.prop{ display:table-cell +} +.prop{ filter:blur(5px) +} +.prop{ filter:brightness(1.075) +} +.prop{ filter:contrast(100%) brightness(50% + 20%) +} +.prop{ float:left +} +.prop{ font-family:"Arial", "Courier New", monospace, sans-serif +} +.prop{ font-family:"MyWebFont" +} +.prop{ font-family:Verdana, Helvetica, sans-serif +} +.prop{ font-size:1.1rem +} +.prop{ font-size:clamp(1.1rem, 0.7153rem + 1.6368vw, 1.5rem) +} +.prop{ font:16px/1.5 'Helvetica Neue', sans-serif +} +.prop{ grid-column:1/-1 +} +.prop{ grid-row:1 / 3 +} +.prop{ grid-template-columns:1fr 2fr 3fr +} +.prop{ grid-template-columns:repeat(3, 1fr + 10px) +} +.prop{ grid-template-columns:subgrid > auto auto +} +.prop{ grid:auto-flow dense / auto auto +} +.prop{ height:calc(100vh * 0.5) +} +.prop{ left:left +} +.prop{ margin:0/* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */ +} +.prop{ margin:0/* p = 'source.css', 'meta.selector.css', 'entity.name.tag.css' */margin:0 +} +.prop{ max-width:calc( var(--size) * 1rem ) +} +.prop{ max-width:calc(1*44px) +} +.prop{ n-_456885sstsats:astien +} +.prop{ outline:3px dashed red +} +.prop{ padding-left:var(--item_margin) +} +.prop{ property-custom:all +} +.prop{ row-gap:5px +} +.prop{ src:url("webfont.woff2") format("woff2"), url("webfont.woff") format("woff") +} +.prop{ text-align:right +} +.prop{ text-decoration:underline +} +.prop{ transform:rotate(45deg) +} +.prop{ transform:translate(10px, 20px) +} +.prop{ unicode-range:u+0-7F +} +.prop{ unicode-range:U+0025-00FF +} +.prop{ url:url(s) +} +.prop{ width:CALC(/**==*/ ltr /**/) +} +.prop{ width:calc(100% - 50px) +} +.prop{ width:calc(50% + 20px) +} +.prop{ width:calc(var(--size) > 50px ? 50px : var(--size)) +} +.prop{ width:clamp(100px, calc(30% / 2rem - 10px), 900px) +} +.prop{ width:clamp(300px, 50%, 800px) +} +.prop{ width:some-edgy-new-function(30% / 2rem + 10px) +} + +.nested-selectors { + :root + {gap:0} + + * + {gap:0} + + & + {gap:0} + + namespace|* + {gap:0} + + |div + {gap:0} + + a + {gap:0} + + pokémon-ピカチュウ + {gap:0} + + very-custom.class, + very-custom:hover, + very-custom::shadow, + Basecamp-schedule basecamp-Schedule + {gap:0} + + halo_night + {gap:0} + + @some-weird-new-feature + {gap:0} + + +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..511e1db --- /dev/null +++ b/package-lock.json @@ -0,0 +1,202 @@ +{ + "name": "css-nesting-syntax-highlighting", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "css-nesting-syntax-highlighting", + "version": "1.0.0", + "devDependencies": { + "cson": "^8.4.0" + }, + "engines": { + "vscode": "^1.84.0" + } + }, + "node_modules/coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true, + "license": "MIT", + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cson": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/cson/-/cson-8.4.0.tgz", + "integrity": "sha512-QwXDbiJodA3DFEKA888zTED28EZo2JC7EK2K3qVsUVbE0oKQVRZ4K+Ch/I8QaTYPZ9Q0GXG1BFK5dtcxd6C3mg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "cson-parser": "^4.0.9", + "extract-opts": "^5.8.0", + "requirefresh": "^5.13.0", + "safefs": "^8.9.0" + }, + "bin": { + "cson": "bin.cjs", + "cson2json": "bin.cjs", + "json2cson": "bin.cjs" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "coffeescript": "1.12.7" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/eachr": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-7.4.0.tgz", + "integrity": "sha512-d6v/ERRrwxZ5oNHJz2Z5Oouge0Xc3rFxeaGNHjAhTDUCkLhy8t583ieH9/Qop1UNDTcZXOSEs8dqawmbHaEEkA==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0", + "typechecker": "^9.3.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/editions": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.21.0.tgz", + "integrity": "sha512-ofkXJtn7z0urokN62DI3SBo/5xAtF0rR7tn+S/bSYV79Ka8pTajIIl+fFQ1q88DQEImymmo97M4azY3WX/nUdg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.13.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/extract-opts": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-5.9.0.tgz", + "integrity": "sha512-UN5d1xtD3cQQNmE2OVsP5ij1dCDgpK5bmOxyMEXc8KaZ/YLCV0ge/RxBGG3fy7KVnxsjekwutzmzRVInQtaB4Q==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "eachr": "^7.4.0", + "editions": "^6.21.0", + "typechecker": "^9.3.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/requirefresh": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-5.13.0.tgz", + "integrity": "sha512-v3BuU/AAKjDqgIig1xyekcBHtujCAghuFTOQ7YNAngEKtTksSS1vT9Abt1ilbmQFAD4ChHHGJ3JSNbqvEOIxkQ==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/safefs": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-8.10.0.tgz", + "integrity": "sha512-ZHnuws4iLc5vB6kxU/MpGEZO7Fz80zIEOJw6VgRAroa/PeJB/jcc2KEXxWA0Izpx+W728j398DxQRx1RU+KXIg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "graceful-fs": "^4.2.11", + "version-compare": "^3.10.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/typechecker": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-9.3.0.tgz", + "integrity": "sha512-7NKr0EkLaL5fkYE56DPwqgQx1FjepvDRZ64trUgb1NgeFqLkaZThI2L33vFJzN4plVyAN5zWcov57QcZIU3bjg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.20.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-compare": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-3.11.0.tgz", + "integrity": "sha512-sufLk/bD3oq+iLzWk8FOHfYwVIaC3NLJcvMqnggmakwNskyKyM+u8x1F8NdwjlLVgC8oYSTgB0RK68VCT6wQOg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-range": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.14.0.tgz", + "integrity": "sha512-gjb0ARm9qlcBAonU4zPwkl9ecKkas+tC2CGwFfptTCWWIVTWY1YUbT2zZKsOAF1jR/tNxxyLwwG0cb42XlYcTg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + } + } +} diff --git a/package.json b/package.json index 7f4fc55..20cd885 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,52 @@ { "name": "css-nesting-syntax-highlighting", "displayName": "CSS Nesting Syntax Highlighting", - "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", - "version": "0.4.0", - "icon": "images/css-nesting-syntax-highlighting-logo.png", + "version": "1.0.0", "publisher": "jacobcassidy", - "bugs": { - "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting/issues" + "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", + "author": { + "name": "Jacob Cassidy" }, - "repository": { - "type": "git", - "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting" + "categories": [ + "Programming Languages" + ], + "contributes": { + "grammars": [ + { + "language": "css", + "path": "./syntaxes/css.tmLanguage.json", + "scopeName": "source.css" + } + ] + }, + "engines": { + "vscode": "^1.84.0" }, "galleryBanner": { "color": "#292929", "theme": "dark" }, + "icon": "images/css-nesting-syntax-highlighting-logo.png", "keywords": [ + "Colorizer", "CSS Nesting", + "Highlighting", + "Nested", "Nested CSS", "Nesting", - "Nested", - "Syntax", - "Highlighting", - "Colorizer" + "Syntax" ], - "engines": { - "vscode": "^1.84.0" + "devDependencies": { + "cson": "^8.4.0" }, - "categories": [ - "Programming Languages" - ], - "contributes": { - "grammars": [ - { - "language": "css", - "scopeName": "source.css", - "path": "./syntaxes/css.tmLanguage.json" - } - ] + "bugs": { + "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting" }, - "__metadata": { - "id": "dc07370c-e087-43f1-91fe-4f44363dc5d7", - "publisherId": "aee9e75c-ea26-4af1-b5e6-815e33288c4b", - "publisherDisplayName": "Jacob Cassidy", - "targetPlatform": "undefined", - "updated": false, - "isPreReleaseVersion": false, - "hasPreReleaseVersion": false, - "installedTimestamp": 1736203061589, - "pinned": false, - "size": 146700, - "isApplicationScoped": false, - "preRelease": false, - "source": "gallery" + "scripts": { + "build": "cson2json src/vscode-css/grammars/css.cson > syntaxes/css.tmLanguage.json" } } diff --git a/src/vscode-css/.editorconfig b/src/vscode-css/.editorconfig new file mode 100644 index 0000000..cd6061a --- /dev/null +++ b/src/vscode-css/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Tab indentation +[*] +indent_style = tab +trim_trailing_whitespace = true + +# The indent size used in the `package.json` file cannot be changed +# https://github.com/npm/npm/pull/3180#issuecomment-16336516 +[{*.yml,*.yaml,*.cson,package.json}] +indent_style = space +indent_size = 2 diff --git a/src/vscode-css/.gitignore b/src/vscode-css/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/src/vscode-css/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/src/vscode-css/.nvmrc b/src/vscode-css/.nvmrc new file mode 100644 index 0000000..2a393af --- /dev/null +++ b/src/vscode-css/.nvmrc @@ -0,0 +1 @@ +20.18.0 diff --git a/src/vscode-css/CONTRIBUTING.md b/src/vscode-css/CONTRIBUTING.md new file mode 100644 index 0000000..a8471f0 --- /dev/null +++ b/src/vscode-css/CONTRIBUTING.md @@ -0,0 +1 @@ +See the [VS Code contributing guide](https://github.com/microsoft/vscode/blob/main/CONTRIBUTING.md) diff --git a/src/vscode-css/LICENSE.txt b/src/vscode-css/LICENSE.txt new file mode 100644 index 0000000..c3fa4ed --- /dev/null +++ b/src/vscode-css/LICENSE.txt @@ -0,0 +1,33 @@ +MIT License + +Copyright (c) Microsoft Corporation. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------- + +This package was derived from a TextMate bundle located at +https://github.com/textmate/css.tmbundle and distributed under the following +license, located in `README.mdown`: + +Permission to copy, use, modify, sell and distribute this +software is granted. This software is provided "as is" without +express or implied warranty, and with no claim as to its +suitability for any purpose. diff --git a/src/vscode-css/README.md b/src/vscode-css/README.md new file mode 100644 index 0000000..54d9dd8 --- /dev/null +++ b/src/vscode-css/README.md @@ -0,0 +1,10 @@ +# CSS syntax highlighting in VS Code + +Adds syntax highlighting to CSS files in VS Code + +Derived from https://github.com/atom/language-css. +Originally [converted](http://flight-manual.atom.io/hacking-atom/sections/converting-from-textmate) +from the [CSS TextMate bundle](https://github.com/textmate/css.tmbundle). + +Contributions are greatly appreciated. Please fork this repository and open a +pull request to add snippets, make grammar tweaks, etc. diff --git a/src/vscode-css/SECURITY.md b/src/vscode-css/SECURITY.md new file mode 100644 index 0000000..9dc6316 --- /dev/null +++ b/src/vscode-css/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson new file mode 100644 index 0000000..8e06d41 --- /dev/null +++ b/src/vscode-css/grammars/css.cson @@ -0,0 +1,2371 @@ +'scopeName': 'source.css' +'name': 'CSS' +'fileTypes': [ + 'css' + 'css.erb' +] +'firstLineMatch': '''(?xi) + # Emacs modeline + -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*) + css + (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + css + (?=\\s|:|$) +''' +'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#combinator-invalid' + } + { + 'include': '#selector' + } + { + 'include': '#at-rules' + } + { + 'include': '#rule-list' + } +] +'repository': + 'arithmetic-operators': + 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' + 'name': 'keyword.operator.arithmetic.css' + 'at-rules': + 'patterns': [ + { + # @charset, with possible preceding BOM sequence + 'begin': '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' + 'end': ';|(?=$)' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.charset.css' + 'patterns': [ + { + 'captures': + '1': + 'name': 'invalid.illegal.not-lowercase.charset.css' + '2': + 'name': 'invalid.illegal.leading-whitespace.charset.css' + '3': + 'name': 'invalid.illegal.no-whitespace.charset.css' + '4': + 'name': 'invalid.illegal.whitespace.charset.css' + '5': + 'name': 'invalid.illegal.not-double-quoted.charset.css' + '6': + 'name': 'invalid.illegal.unclosed-string.charset.css' + '7': + 'name': 'invalid.illegal.unexpected-characters.charset.css' + 'match': '''(?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + 'captures': + '1': + 'name': 'keyword.control.at-rule.charset.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'match': '((@)charset)(?=\\s)' + } + { + 'begin': '"' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.string.begin.css' + 'end': '"|$' + 'endCaptures': + '0': + 'name': 'punctuation.definition.string.end.css' + 'name': 'string.quoted.double.css' + 'patterns': [ + { + 'begin': '(?:\\G|^)(?=(?:[^"])+$)' + 'end': '$' + 'name': 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + } + { + # @import + 'begin': '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.import.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': ';' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.import.css' + 'patterns': [ + { + 'begin': '\\G\\s*(?=/\\*)' + 'end': '(?<=\\*/)\\s*' + 'patterns': [ + { + 'include': '#comment-block' + } + ] + } + { + 'include': '#string' + } + { + 'include': '#url' + } + { + 'include': '#media-query-list' + } + ] + } + { + # @font-face + 'begin': '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.font-face.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?!\\G)' + 'name': 'meta.at-rule.font-face.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list' + } + ] + } + { + # @page + 'begin': '(?i)(@)page(?=[\\s:{]|/\\*|$)' + 'captures': + '0': + 'name': 'keyword.control.at-rule.page.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*($|[:{;]))' + 'name': 'meta.at-rule.page.css' + 'patterns': [ + { + 'include': '#rule-list' + } + ] + } + { + # @media + 'begin': '(?i)(?=@media(\\s|\\(|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)media' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.media.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.media.header.css' + 'patterns': [ + { + 'include': '#media-query-list' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.media.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.media.end.bracket.curly.css' + 'name': 'meta.at-rule.media.body.css' + 'patterns': [ + { + 'include': '#rule-list-innards' + } + { + 'include': '$self' + } + ] + } + ] + } + { + # @counter-style + 'begin': '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)counter-style' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.counter-style.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*{)' + 'name': 'meta.at-rule.counter-style.header.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'captures': + '0': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + 'name': 'variable.parameter.style-name.css' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.property-list.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.property-list.end.bracket.curly.css' + 'name': 'meta.at-rule.counter-style.body.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list-innards' + } + ] + } + ] + } + { + # @document + 'begin': '(?i)(?=@document([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)document' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.document.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.document.header.css' + 'patterns': [ + { + 'begin': '(?i)(?>|>|\\+|~' + 'name': 'keyword.operator.combinator.css' + } + ] + 'combinator-invalid': + 'match': '/deep/|>>>' + 'name': 'invalid.deprecated.combinator.css' + 'commas': + 'match': ',' + 'name': 'punctuation.separator.list.comma.css' + 'comment-block': + 'begin': '/\\*' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.comment.begin.css' + 'end': '\\*/' + 'endCaptures': + '0': + 'name': 'punctuation.definition.comment.end.css' + 'name': 'comment.block.css' + 'escapes': + 'patterns': [ + { + 'match': '\\\\[0-9a-fA-F]{1,6}' + 'name': 'constant.character.escape.codepoint.css' + } + { + 'begin': '\\\\$\\s*' + 'end': '^(?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + 'match': '''(?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + ) + (?=\\s|\\)|$) + ''' + 'name': 'support.constant.property-value.css' + 'media-query': + 'begin': '\\G' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#media-types' + } + { + 'match': '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + 'name': 'keyword.operator.logical.$1.media.css' + } + { + 'match': '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + 'name': 'keyword.operator.logical.and.media.css' + } + { + 'match': ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + 'name': 'invalid.illegal.comma.css' + } + { + 'include': '#commas' + } + { + 'begin': '\\(' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.parameters.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.parameters.end.bracket.round.css' + 'patterns': [ + { + 'include': '#media-features' + } + { + 'include': '#media-feature-keywords' + } + { + 'match': ':' + 'name': 'punctuation.separator.key-value.css' + } + { + 'match': '>=|<=|=|<|>' + 'name': 'keyword.operator.comparison.css' + } + { + 'captures': + '1': + 'name': 'constant.numeric.css' + '2': + 'name': 'keyword.operator.arithmetic.css' + '3': + 'name': 'constant.numeric.css' + 'match': '(\\d+)\\s*(/)\\s*(\\d+)' + 'name': 'meta.ratio.css' + } + { + 'include': '#numeric-values' + } + { + 'include': '#comment-block' + } + { + 'include': '#functions' + } + ] + } + ] + 'media-query-list': + 'begin': '(?=\\s*[^{;])' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#media-query' + } + ] + 'media-types': + 'captures': + '1': + 'name': 'support.constant.media.css' + '2': + 'name': 'invalid.deprecated.constant.media.css' + 'match': '''(?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + 'numeric-values': + 'patterns': [ + { + 'captures': + '1': + 'name': 'punctuation.definition.constant.css' + 'match': '(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b' + 'name': 'constant.other.color.rgb-value.hex.css' + } + { + 'captures': + '1': + 'name': 'keyword.other.unit.percentage.css' + '2': + 'name': 'keyword.other.unit.${2:/downcase}.css' + 'match': '''(?xi) (?~+] # Selector combinator + | \\| # Selector namespace separator + | \\[ # Attribute selector opening bracket + | [a-zA-Z] # Letter + | [^\\x00-\\x7F] # Non-ASCII symbols + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + + # Or one of the following symbols, followed by a word character, hyphen, or escape sequence: + | (?: + \\. # Class selector + | \\# # ID selector + ) + (?: + [\\w-] # Word character or hyphen + | \\\\(?: # Escape sequence + [0-9a-fA-F]{1,6} + | . + ) + ) + + # Or one of the following symbols, followed a letter or hyphen: + | (?: + \\: # Pseudo-class + | \\:\\: # Pseudo-element + ) + [a-zA-Z-] # Letter or hyphen + ) + ) + + # Match must NOT contain any of the following: + (?! + [\\w-]*[\\:]+\\s # One or more colons immediately followed by a whitespace (denotes a property or invalid sequence) + | [^{]*; # Any characters and a semicolon before an opening bracket (denotes the end of a property) + | [^{]*} # A closing bracket before an opening bracket (denotes a property) + | [^\\:]+(\\:\\:):+ # More than two colons (invalid sequence) + ) + ''' + 'end': '''(?x) + # Match must end with: + (?= + \\s* # Optional whitespace and one of the following: + (?: + \\/ # Comment + | @ # At-rule + | { # Opening property list brace + | \\) # Closing function brace (for passing test on `some-edgy-new-function(`) + | $ # End of line + ) + ) + ''' + 'name': 'meta.selector.css' + 'patterns': [ + { + 'include': '#selector-innards' + } + ] + 'selector-innards': + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#commas' + } + { + 'include': '#escapes' + } + { + 'include': '#combinators' + } + { + 'captures': + '1': + 'name': 'entity.other.namespace-prefix.css' + '2': + 'name': 'punctuation.separator.css' + 'match': '''(?x) + (?:^|(?<=[\\s,(};])) # Follows whitespace, comma, semicolon, or bracket + (?! + [-\\w*]+ + \\| + (?! + [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match + | [^\\x00-\\x7F] + ) + ) + ( + (?: [-a-zA-Z_] | [^\\x00-\\x7F] ) # First letter + (?: [-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + | \\\\(?:[0-9a-fA-F]{1,6}|.) + )* + | + \\* # Universal namespace + )? + (\\|) # Namespace separator + ''' + } + { + 'include': '#tag-names' + } + { + 'match': '&' + 'name': 'entity.name.tag.nesting.css' + } + { + 'match': '\\*' + 'name': 'entity.name.tag.wildcard.css' + } + { + 'captures': + '1': + 'name': 'punctuation.definition.entity.css' + '2': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) (?+~|&] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%(*;+~|&] # - Another selector + | /\\* # - A block comment + ) + ''' + 'name': 'entity.other.attribute-name.class.css' + } + { + 'captures': + '1': + 'name': 'punctuation.definition.entity.css' + '2': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (\\#) + ( + -? + (?![0-9]) + (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + ) + (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) + ''' + 'name': 'entity.other.attribute-name.id.css' + } + { + 'begin': '\\[' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.entity.begin.bracket.square.css' + 'end': '\\]' + 'endCaptures': + '0': + 'name': 'punctuation.definition.entity.end.bracket.square.css' + 'name': 'meta.attribute-selector.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#string' + } + { + 'captures': + '1': + 'name': 'storage.modifier.ignore-case.css' + 'match': '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + 'captures': + '1': + 'name': 'string.unquoted.attribute-value.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + 'include': '#escapes' + } + { + 'match': '[~|^$*]?=' + 'name': 'keyword.operator.pattern.css' + } + { + 'match': '\\|' + 'name': 'punctuation.separator.css' + } + { + 'captures': + '1': + 'name': 'entity.other.namespace-prefix.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + 'captures': + '1': + 'name': 'entity.other.attribute-name.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + } + { + 'include': '#pseudo-classes' + } + { + 'include': '#pseudo-elements' + } + { + 'include': '#functional-pseudo-classes' + } + # Custom HTML elements + { + 'match': '''(?x) (?\\s,.\\#|&){:\\[]|/\\*|$) + ''' + 'name': 'entity.name.tag.css' + 'unicode-range': + 'captures': + '0': + 'name': 'constant.other.unicode-range.css' + '1': + 'name': 'punctuation.separator.dash.unicode-range.css' + 'match': '(?=6" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "dependencies": { + "coffeescript": "1.12.7" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/cson-parser/node_modules/coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true, + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eachr": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-7.2.0.tgz", + "integrity": "sha512-zv0Vtm0c0mvTvamW3duiGqadnu4w9oIpG7p8YLb2z7bDj5wGRvzNJTLyMeWiuOvYXj9kT411ox0UAa8fziUIeg==", + "dev": true, + "dependencies": { + "editions": "^6.19.0", + "typechecker": "^9.2.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/editions": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.19.0.tgz", + "integrity": "sha512-mJabiGaqoKWjgz0kqrVPzLBsk4sh11zcNXKFXBZ3eLwwHBWylz5OcrBrZRRaLPWoRpm9m428CLzKcituabUuFg==", + "dev": true, + "dependencies": { + "version-range": "^4.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/extract-opts": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-5.5.0.tgz", + "integrity": "sha512-idbs8/KK4ZFB+Q5m5mCK8rMOOfwtEvfn/60q3zN8HzIlvn/kGdcqV+IbAqqJwtYPm1TvbbSJZxcyOYEXvn2zuA==", + "dev": true, + "dependencies": { + "eachr": "^7.2.0", + "editions": "^6.19.0", + "typechecker": "^9.2.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/requirefresh": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-5.11.0.tgz", + "integrity": "sha512-S1yRp4++ZZEAJv0ecPZotN62XXH38s/WDqoqWK3JZ3zqEmUb1RoqsYGCF+xN8DRGE3oecfdRF2AgF0nnuvUD3g==", + "dev": true, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/safefs": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-8.6.0.tgz", + "integrity": "sha512-CfelnCnGgCHWylnMTG68qYgFxu6mrnamaiCBQe6YDpnzrooLFfROMrkJBkQGlTU83GUcn+VBkYfZyEMt4ygPVA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.11", + "version-compare": "^3.8.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/typechecker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-9.2.0.tgz", + "integrity": "sha512-WrIeSNqD6gW0Ss8OPHB0xHLyihoCsE57NZ5aLeYkLG75SOwP2uE9MtAnqKyca48X70vuJ3SNiSMRtop7Dnmg8A==", + "dev": true, + "dependencies": { + "editions": "^6.19.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-compare": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-3.8.0.tgz", + "integrity": "sha512-90rv+kdT8VyU45h6hOQeP9w8EuxHDdBOzHbHZZKrhw3zpaK3nJXgKDSCkV/OlUgvmt7vprCB1R9VuHrgjffpIg==", + "dev": true, + "dependencies": { + "editions": "^6.18.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-range": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.11.0.tgz", + "integrity": "sha512-h6ZOpIbN1Dk6hfDAmni/lOMncSUcIU1/6XsDc54kQe9/lMzzzv53ovpTr0CbNDJz2GLbmr/yyrQjD8vntyxXxg==", + "dev": true, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vscode-oniguruma": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz", + "integrity": "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.0.0.tgz", + "integrity": "sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==", + "dev": true + } + }, + "dependencies": { + "cson": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cson/-/cson-8.2.0.tgz", + "integrity": "sha512-Hkf2J3i9VOGqlIYbjHaaHW9Vz9kJXebG1VrCivK76J1vr8uj4HG+z+UmJFxl8xVanpWfybUdk8l293dSDxwIWg==", + "dev": true, + "requires": { + "cson-parser": "^4.0.9", + "extract-opts": "^5.3.0", + "requirefresh": "^5.9.0", + "safefs": "^8.4.0" + } + }, + "cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "requires": { + "coffeescript": "1.12.7" + }, + "dependencies": { + "coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true + } + } + }, + "eachr": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-7.2.0.tgz", + "integrity": "sha512-zv0Vtm0c0mvTvamW3duiGqadnu4w9oIpG7p8YLb2z7bDj5wGRvzNJTLyMeWiuOvYXj9kT411ox0UAa8fziUIeg==", + "dev": true, + "requires": { + "editions": "^6.19.0", + "typechecker": "^9.2.0" + } + }, + "editions": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.19.0.tgz", + "integrity": "sha512-mJabiGaqoKWjgz0kqrVPzLBsk4sh11zcNXKFXBZ3eLwwHBWylz5OcrBrZRRaLPWoRpm9m428CLzKcituabUuFg==", + "dev": true, + "requires": { + "version-range": "^4.11.0" + } + }, + "extract-opts": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-5.5.0.tgz", + "integrity": "sha512-idbs8/KK4ZFB+Q5m5mCK8rMOOfwtEvfn/60q3zN8HzIlvn/kGdcqV+IbAqqJwtYPm1TvbbSJZxcyOYEXvn2zuA==", + "dev": true, + "requires": { + "eachr": "^7.2.0", + "editions": "^6.19.0", + "typechecker": "^9.2.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "requirefresh": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-5.11.0.tgz", + "integrity": "sha512-S1yRp4++ZZEAJv0ecPZotN62XXH38s/WDqoqWK3JZ3zqEmUb1RoqsYGCF+xN8DRGE3oecfdRF2AgF0nnuvUD3g==", + "dev": true + }, + "safefs": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-8.6.0.tgz", + "integrity": "sha512-CfelnCnGgCHWylnMTG68qYgFxu6mrnamaiCBQe6YDpnzrooLFfROMrkJBkQGlTU83GUcn+VBkYfZyEMt4ygPVA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.11", + "version-compare": "^3.8.0" + } + }, + "typechecker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-9.2.0.tgz", + "integrity": "sha512-WrIeSNqD6gW0Ss8OPHB0xHLyihoCsE57NZ5aLeYkLG75SOwP2uE9MtAnqKyca48X70vuJ3SNiSMRtop7Dnmg8A==", + "dev": true, + "requires": { + "editions": "^6.19.0" + } + }, + "version-compare": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-3.8.0.tgz", + "integrity": "sha512-90rv+kdT8VyU45h6hOQeP9w8EuxHDdBOzHbHZZKrhw3zpaK3nJXgKDSCkV/OlUgvmt7vprCB1R9VuHrgjffpIg==", + "dev": true, + "requires": { + "editions": "^6.18.0" + } + }, + "version-range": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.11.0.tgz", + "integrity": "sha512-h6ZOpIbN1Dk6hfDAmni/lOMncSUcIU1/6XsDc54kQe9/lMzzzv53ovpTr0CbNDJz2GLbmr/yyrQjD8vntyxXxg==", + "dev": true + }, + "vscode-oniguruma": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz", + "integrity": "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==", + "dev": true + }, + "vscode-textmate": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.0.0.tgz", + "integrity": "sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==", + "dev": true + } + } +} diff --git a/src/vscode-css/package.json b/src/vscode-css/package.json new file mode 100644 index 0000000..c6cae0a --- /dev/null +++ b/src/vscode-css/package.json @@ -0,0 +1,13 @@ +{ + "name": "vscode-css", + "description": "CSS support in VS Code", + "version": "0.0.0", + "scripts": { + "test": "node --test ./spec/css-spec.mjs" + }, + "devDependencies": { + "cson": "^8.2.0", + "vscode-oniguruma": "^2.0.1", + "vscode-textmate": "^9.0.0" + } +} diff --git a/src/vscode-css/spec/css-spec.mjs b/src/vscode-css/spec/css-spec.mjs new file mode 100644 index 0000000..1696b2c --- /dev/null +++ b/src/vscode-css/spec/css-spec.mjs @@ -0,0 +1,3693 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import testGrammar from '../testing-util/test.mjs'; + +describe('CSS grammar', function () { + it('parses the grammar', function () { + assert.ok(testGrammar.grammar); + assert.equal(testGrammar.grammar._grammar.scopeName, 'source.css'); + }); + + describe('selectors', function () { + it('tokenizes type selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + }); + + it('tokenizes the universal selector', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + }); + + it('tokenises combinators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a > b + * ~ :not(.nah)').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '+' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + }); + + it('highlights deprecated combinators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.sooo /deep/ >>>_.>>>').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'invalid.deprecated.combinator.css'], value: '/deep/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'invalid.deprecated.combinator.css'], value: '>>>' }); + }); + + it('tokenizes complex selectors', function () { + var lines, tokens; + tokens = testGrammar.tokenizeLine('[disabled], [disabled] + p').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.begin.bracket.square.css"], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "entity.other.attribute-name.css"], value: 'disabled' }); + assert.deepStrictEqual(tokens[2], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.end.bracket.square.css"], value: ']' }); + assert.deepStrictEqual(tokens[3], { scopes: ["source.css", "meta.selector.css", "punctuation.separator.list.comma.css"], value: ',' }); + assert.deepStrictEqual(tokens[5], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.begin.bracket.square.css"], value: '[' }); + assert.deepStrictEqual(tokens[6], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "entity.other.attribute-name.css"], value: 'disabled' }); + assert.deepStrictEqual(tokens[7], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.end.bracket.square.css"], value: ']' }); + assert.deepStrictEqual(tokens[9], { scopes: ["source.css", "meta.selector.css", "keyword.operator.combinator.css"], value: '+' }); + assert.deepStrictEqual(tokens[11], { scopes: ["source.css", "meta.selector.css", "entity.name.tag.css"], value: 'p' }); + + lines = testGrammar.tokenizeLines("[disabled]:not(:first-child)::before:hover\n ~ div.object\n + #id.thing:hover > strong ~ p::before,\na::last-of-type,/*Comment*/::selection > html[lang^=en-AU],\n*>em.i.ly[data-name|=\"Life\"] { }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'disabled' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first-child' }); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[0][9], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'before' }); + assert.deepStrictEqual(lines[0][11], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][12], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'hover' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'div' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'object' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '+' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'id' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'thing' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'hover' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'strong' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'before' }); + assert.deepStrictEqual(lines[2][19], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'invalid.illegal.colon.css'], value: ':' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'last-of-type' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'comment.block.css'], value: 'Comment' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'selection' }); + assert.deepStrictEqual(lines[3][11], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[3][13], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'html' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[3][15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'en-AU' }); + assert.deepStrictEqual(lines[3][18], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[3][19], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'em' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'i' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'ly' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'data-name' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'Life' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[4][15], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][17], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + describe('custom elements (as type selectors)', function () { + it('only tokenizes identifiers beginning with [a-z]', function () { + var tokens; + tokens = testGrammar.tokenizeLine('pearl-1941 1941-pearl -pearl-1941').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'pearl-1941' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css'], value: ' 1941-pearl -pearl-1941' }); + }); + + it('tokenizes custom elements containing non-ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('pokémon-ピカチュウ').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'pokémon-ピカチュウ' }); + }); + + it('does not tokenize identifiers containing [A-Z]', function () { + var tokens; + tokens = testGrammar.tokenizeLine('Basecamp-schedule basecamp-Schedule').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: 'Basecamp-schedule basecamp-Schedule' }); + }); + + it('does not tokenize identifiers containing no hyphens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('halo_night').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: 'halo_night' }); + }); + + it('does not tokenise identifiers following an @ symbol', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@some-weird-new-feature').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'some-weird-new-feature' }); + }); + + it('does not tokenise identifiers in unfamiliar functions', function () { + var tokens; + tokens = testGrammar.tokenizeLine('some-edgy-new-function()').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: 'some-edgy-new-function(' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ')' }); + }); + }); + + describe('attribute selectors', function () { + it('tokenizes attribute selectors without values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[title]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'title' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes attribute selectors with identifier values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[hreflang|=fr]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'hreflang' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'fr' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes attribute selectors with string values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[href^="http://www.w3.org/"]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'href' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'http://www.w3.org/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes CSS qualified attribute names with wildcard prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[*|title]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'title' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes CSS qualified attribute names with namespace prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[marvel|origin=radiation]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'marvel' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'origin' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'radiation' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes CSS qualified attribute names without namespace prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[|data-hp="75"]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'data-hp' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '75' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises compound ID/attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#div[id="0"]{ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'div' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine('.bar#div[id="0"]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'bar' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'div' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + }); + + it('tokenises compound class/attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.div[id="0"]{ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'div' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine('#bar.div[id]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'bar' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'div' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('allows whitespace to be inserted between tokens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('span[ er|lang |= "%%" ]').tokens; + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'er' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '%%' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises escape sequences inside attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[name\\[0\\]="value"]').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'name' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\[' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: '0' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\]' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises escape sequences inside namespace prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[name\\ space|Get\\ It\\?="kek"]').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'name' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'space' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'Get' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'It' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\?' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises comments inside attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('span[/*]*/lang]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'span' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: ']' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises quoted strings in attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[href^="#"] a[href^= "#"] a[href^="#" ]').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '#' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '#' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '#' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine("a[href^='#'] a[href^= '#'] a[href^='#' ]").tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '#' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '#' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '#' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises unquoted strings in attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('span[class~=Java]').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '~=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'Java' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine('span[class^= 0xDEADCAFE=|~BEEFBABE ]').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: '0xDEADCAFE=|~BEEFBABE' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises escape sequences in unquoted strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[name\\[0\\]=a\\BAD\\AF\\]a\\ i] {}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'a' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.codepoint.css'], value: '\\BAD' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.codepoint.css'], value: '\\AF' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.css'], value: '\\]' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'a' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'i' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises the ignore-case modifier at the end of a selector', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[attr=val i] a[attr="val" i] a[attr=\'val\'I] a[val^= \'"\'i] a[attr= i] a[attr= i i]').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'I' }); + assert.deepStrictEqual(tokens[28], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[34], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[35], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[36], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '"' }); + assert.deepStrictEqual(tokens[37], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[38], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[39], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[44], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[45], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[46], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'i' }); + assert.deepStrictEqual(tokens[47], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[52], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[53], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[54], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'i' }); + assert.deepStrictEqual(tokens[55], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[56], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[57], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises attribute selectors spanning multiple lines', function () { + var lines; + lines = testGrammar.tokenizeLines("span[\n \\x20{2}\n ns|lang/**/\n |=\n\"pt\"]"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'span' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'ns' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'pt' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + lines = testGrammar.tokenizeLines("span[/*===\n==|span[/*}\n====*/*|lang/*]=*/~=/*\"|\"*/\"en-AU\"/*\n |\n*/\ni]"); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '===' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '==|span[/*}' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '====' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: ']=' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '~=' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '"|"' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[2][13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'en-AU' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: ' |' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + }); + + describe('class selectors', function () { + it('tokenizes class selectors containing non-ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.étendard').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'étendard' }); + + tokens = testGrammar.tokenizeLine('.スポンサー').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'スポンサー' }); + }); + + it('tokenizes a class selector consisting of two hypens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.--').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: '--' }); + }); + + it('tokenizes class selectors consisting of one (valid) character', function () { + var tokens; + tokens = testGrammar.tokenizeLine('._').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: '_' }); + }); + + it('tokenises class selectors starting with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.\\33\\44-model {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.codepoint.css'], value: '\\33' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.codepoint.css'], value: '\\44' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: '-model' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises class selectors ending with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.la\\{tex\\} {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'la' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.css'], value: '\\{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'tex' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.css'], value: '\\}' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it contains unescaped ASCII punctuation or symbols other than "-" and "_"', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.B!W{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: 'B!W' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it starts with ASCII digits ([0-9])', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.666{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '666' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it starts with "-" followed by ASCII digits', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.-911-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-911-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it consists of only one hyphen', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + }); + + describe('id selectors', function () { + it('tokenizes id selectors consisting of ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#unicorn').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'unicorn' }); + }); + + it('tokenizes id selectors containing non-ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#洪荒之力').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '洪荒之力' }); + }); + + it('tokenizes id selectors containing [0-9], "-", or "_"', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#_zer0-day').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '_zer0-day' }); + }); + + it('tokenizes id selectors beginning with two hyphens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#--d3bug--').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '--d3bug--' }); + }); + + it('marks an id invalid if it contains ASCII punctuation or symbols other than "-" and "_"', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#sort!{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: 'sort!' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks an id invalid if it starts with ASCII digits ([0-9])', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#666{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '666' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks an id invalid if it starts with "-" followed by ASCII digits', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#-911-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-911-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks an id invalid if it consists of one hyphen only', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises ID selectors starting with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#\\33\\44-model {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.codepoint.css'], value: '\\33' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.codepoint.css'], value: '\\44' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '-model' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises ID selectors ending with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#la\\{tex\\} {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'la' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.css'], value: '\\{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'tex' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.css'], value: '\\}' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + }); + + describe('namespace prefixes', function () { + it('tokenises arbitrary namespace prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('foo|h1 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: 'foo' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises anonymous namespace prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*|abbr {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'abbr' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('*|* {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('foo|* { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: 'foo' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('|[svg|attr=name]{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'svg' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'attr' }); + }); + + it('tokenises the "no-namespace" prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('|h1 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("doesn't tokenise prefixes without a selector", function () { + var tokens; + tokens = testGrammar.tokenizeLine('*| { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + tokens = testGrammar.tokenizeLine('*|{ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('at-rules', function () { + describe('@charset', function () { + it('tokenises @charset rules at the start of a file', function () { + var lines; + lines = testGrammar.tokenizeLines('@charset "US-ASCII";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'US-ASCII' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('/* Not the first line */\n@charset "UTF-8";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'comment.block.css'], value: ' Not the first line ' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'charset' }); + }); + + it('highlights invalid @charset statements', function () { + var lines; + lines = testGrammar.tokenizeLines(" @charset 'US-ASCII';"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.leading-whitespace.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.not-double-quoted.charset.css'], value: "'US-ASCII'" }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "iso-8859-15";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.whitespace.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'iso-8859-15' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset"US-ASCII";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.no-whitespace.charset.css'], value: '@charset"US-ASCII"' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "UTF-8" ;'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'UTF-8' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.unexpected-characters.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "WTF-8" /* Nope */ ;'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'WTF-8' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.unexpected-characters.charset.css'], value: ' /* Nope */ ' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "UTF-8'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.unclosed-string.charset.css'], value: '"UTF-8' }); + + lines = testGrammar.tokenizeLines("@CHARSET 'US-ASCII';"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.not-lowercase.charset.css'], value: '@CHARSET' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: " 'US-ASCII'" }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + describe('@import', function () { + it('tokenises @import statements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import url("file.css");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import "file.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine("@import 'file.css';").tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.single.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + }); + it("doesn't let injected comments impact parameter matching", function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import /* url("name"); */ "1.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url("name"); ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: '1.css' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import/* Comment */"2.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' Comment ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: '2.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + it('correctly handles word boundaries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import"file.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import-file.css;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'import-file' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: '.css' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.header.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('matches a URL that starts on the next line', function () { + var lines; + lines = testGrammar.tokenizeLines('@import\nurl("file.css");'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + // Skipped because `vscode-textmate` does not produce this token + it.skip('matches a URL that starts on the next line and produces a token for whitespace', function () { + var lines; + lines = testGrammar.tokenizeLines('@import\nurl("file.css");'); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.import.css'], value: '' }); + }); + + it('matches comments inside query lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import url("1.css") print /* url(";"); */ all;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: '1.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url(";"); ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'all' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('highlights deprecated media types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import "astral.css" projection;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: 'astral.css' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'invalid.deprecated.constant.media.css'], value: 'projection' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('highlights media features in query lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import url(\'landscape.css\') screen and (orientation:landscape);').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: '\'' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.single.css'], value: 'landscape.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: '\'' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.type.property-name.media.css'], value: 'orientation' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.property-value.css'], value: 'landscape' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + + describe('@media', function () { + it('tokenises @media keywords correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media(max-width: 37.5em) { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '37.5' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('@media not print and (max-width: 37.5em){ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.not.media.css'], value: 'not' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '37.5' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + it('highlights deprecated media types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (max-device-width: 2px){ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-device-width' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('highlights vendored media features', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (-webkit-foo: bar){ b{ } }').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-webkit-foo' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: ' bar' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + + tokens = testGrammar.tokenizeLine('@media screen and (-ms-high-contrast:black-on-white){ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-ms-high-contrast' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'black-on-white' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('@media (_moz-a:b){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '_moz-a' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'b' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('@media (-hp-foo:bar){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-hp-foo' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'bar' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('@media (mso-page-size:wide){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: 'mso-page-size' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'wide' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises @media immediately following a closing brace', function () { + var tokens; + tokens = testGrammar.tokenizeLine('h1 { }@media only screen { } h2 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h2' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('h1 { }@media only screen { }h2 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h2' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises level 4 media-query syntax', function () { + var lines; + lines = testGrammar.tokenizeLines("@media (min-width >= 0px)\n and (max-width <= 400)\n and (min-height > 400)\n and (max-height < 200)"); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '>=' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '<=' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '>' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '<' }); + }); + + it('tokenises comments between media types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media/* */only/* */screen/* */and (min-width:1100px){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-width' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '1100' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises comments between media features', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media/*=*/(max-width:/**/37.5em)/*=*/and/*=*/(/*=*/min-height/*:*/:/*=*/1.2em/*;*/){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '37.5' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-height' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ':' }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[28], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[29], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[30], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[31], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[32], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '1.2' }); + assert.deepStrictEqual(tokens[33], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[34], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[35], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ';' }); + assert.deepStrictEqual(tokens[36], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[37], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[38], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[39], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + }); + + it('matches media queries across lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@media only screen and (min-width : /* 40 */\n 320px),\n not print and (max-width: 480px) /* kek */ and (-webkit-min-device-pixel-ratio /*:*/ : 2),\nonly speech and (min-width: 10em), /* wat */ (-webkit-min-device-pixel-ratio: 2) { }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[0][9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-width' }); + assert.deepStrictEqual(lines[0][12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[0][14], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' 40 ' }); + assert.deepStrictEqual(lines[0][16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '320' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.not.media.css'], value: 'not' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '480' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' kek ' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[2][21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][22], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-webkit-min-device-pixel-ratio' }); + assert.deepStrictEqual(lines[2][24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][25], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ':' }); + assert.deepStrictEqual(lines[2][26], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][28], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][30], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[2][31], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][32], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'speech' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-width' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '10' }); + assert.deepStrictEqual(lines[3][11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[3][12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' wat ' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-webkit-min-device-pixel-ratio' }); + assert.deepStrictEqual(lines[3][21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][23], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[3][24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][26], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[3][28], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('highlights invalid commas', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media , {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'invalid.illegal.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('@media , ,screen {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'invalid.illegal.comma.css'], value: ', ,' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('allows spaces inside ratio values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (min-aspect-ratio: 3 / 4) and (max-aspect-ratio: 20 / 17) {}').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '3' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '4' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '17' }); + }); + + describe('@keyframes', function () { + it('tokenises keyframe lists correctly', function () { + var lines; + lines = testGrammar.tokenizeLines("@keyframes important1 {\n from { margin-top: 50px;\n margin-bottom: 100px }\n 50% { margin-top: 150px !important; } /* Ignored */\n to { margin-top: 100px; }\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'important1' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'from' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-bottom' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '50%' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '150' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[3][11], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'keyword.other.important.css'], value: '!important' }); + assert.deepStrictEqual(lines[3][12], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css'], value: ' Ignored ' }); + assert.deepStrictEqual(lines[3][18], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'to' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.end.bracket.curly.css'], value: '}' }); + }); + + it('matches injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("@keyframes/*{*/___IDENT__/*}\n { Nah { margin-top: 2em; }\n*/{ from"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: '___IDENT__' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css'], value: ' { Nah { margin-top: 2em; }' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'from' }); + }); + + it('matches offset keywords case-insensitively', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@keyframes Give-them-both { fROm { } To {} }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'Give-them-both' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'fROm' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'To' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.end.bracket.curly.css'], value: '}' }); + }); + + it('matches percentile offsets', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@keyframes identifier { -50.2% } @keyframes ident2 { .25%}').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '-50.2%' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '.25%' }); + }); + + it('highlights escape sequences inside identifiers', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@keyframes A\\1F602Z').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'A' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css', 'constant.character.escape.codepoint.css'], value: '\\1F602' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'Z' }); + }); + }); + + describe('@supports', function () { + it('tokenises feature queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@supports (font-size: 1em) { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.supports.header.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'font-size' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('matches logical operators', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports not (font-size: 1em){ }\n@supports (font-size: 1em) and (font-size: 1em){ }\n@supports (font-size: 1em) or (font-size: 1em){ }"); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.not.css'], value: 'not' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.and.css'], value: 'and' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + }); + + it('matches custom variables in feature queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@supports (--foo: green){}').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'variable.css'], value: '--foo' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'green' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + }); + + it("doesn't mistake brackets in string literals for feature queries", function () { + var lines; + lines = testGrammar.tokenizeLines("@supports not ((tab-size:4) or (-moz-tab-size:4)){\n body::before{content: \"Come on, Microsoft (Get it together already)…\"; }\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.not.css'], value: 'not' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'tab-size' }); + assert.deepStrictEqual(lines[0][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[0][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-moz-tab-size' }); + assert.deepStrictEqual(lines[0][20], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'body' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'before' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'Come on, Microsoft (Get it together already)…' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises multiple feature queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@supports (display:table-cell) or ((display:list-item) and (display:run-in)){').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'table-cell' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'list-item' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'keyword.operator.logical.feature.and.css'], value: 'and' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'run-in' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + }); + + it('embeds rulesets and other at-rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports (animation-name: test) {\n #node {\n animation-name: test;\n }\n body > header[data-name=\"attr\"] ~ *:not(:first-child){\n content: \"😂👌\"\n }\n @keyframes important1 {\n from {\n margin-top: 50px;\n margin-bottom: 100px\n }\n 50% { margin-top: 150px !important; } /* Ignored */\n to { margin-top: 100px; }\n }\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'animation-name' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css'], value: 'test' }); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'node' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'animation-name' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'test' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'body' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'header' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'data-name' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'attr' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + assert.deepStrictEqual(lines[4][16], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(lines[4][17], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[4][18], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(lines[4][19], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][20], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[4][21], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first-child' }); + assert.deepStrictEqual(lines[4][22], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][23], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[5][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: '😂👌' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[6][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[7][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[7][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(lines[7][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'important1' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[8][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'from' }); + assert.deepStrictEqual(lines[8][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[9][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[9][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[9][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[9][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[9][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[10][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-bottom' }); + assert.deepStrictEqual(lines[10][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[10][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[10][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[11][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[12][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '50%' }); + assert.deepStrictEqual(lines[12][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[12][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[12][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[12][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '150' }); + assert.deepStrictEqual(lines[12][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[12][11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'keyword.other.important.css'], value: '!important' }); + assert.deepStrictEqual(lines[12][12], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[12][14], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[12][16], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[12][17], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css'], value: ' Ignored ' }); + assert.deepStrictEqual(lines[12][18], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[13][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'to' }); + assert.deepStrictEqual(lines[13][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[13][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[13][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[13][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[13][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[13][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[13][12], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[14][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[15][0], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('matches injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports/*===*/not/*==****************|\n==*/(display:table-cell)/*============*/ and (display: list-item)/*}*/{}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '===' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.not.css'], value: 'not' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '==****************|' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '==' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'table-cell' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '============' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.and.css'], value: 'and' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][19], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][20], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(lines[1][21], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][22], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][23], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('matches feature queries across multiple lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports\n (box-shadow: 0 0 2px rgba(0,0,0,.5) inset) or\n (-moz-box-shadow: 0 0 2px black inset) or\n (-webkit-box-shadow: 0 0 2px black inset) or\n (-o-box-shadow: 0 0 2px black inset)\n{ .noticebox { } }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'box-shadow' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][16], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][17], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][18], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][19], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][20], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.5' }); + assert.deepStrictEqual(lines[1][21], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][23], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[1][24], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][26], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-moz-box-shadow' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'black' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-webkit-box-shadow' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[3][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'black' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[3][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-o-box-shadow' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'black' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[4][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[5][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'noticebox' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][7], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('@namespace', function () { + it('tokenises @namespace statements correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace "XML";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@namespace prefix "XML" ;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'prefix' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@namespace url("http://a.bc/");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'http://a.bc/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it("doesn't confuse a prefix of 'url' as a function", function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace url url("http://a.bc/");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'url' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'http://a.bc/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('permits injected comments between tokens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace/*=*/pre/*=*/"url"/*=*/;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'pre' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'url' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('allows no spaces between "@namespace" and quoted URLs', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace"XML";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('tokenises escape sequences in prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace pre\\ fix "http://url/";').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'pre' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'fix' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + }); + + it('allows arguments to span multiple lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@namespace\nprefix\"XML\";"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'prefix' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines("@namespace\n\n prefix\n\nurl(\"http://a.bc/\");"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'prefix' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'http://a.bc/' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + + describe('font-feature declarations', function () { + it('tokenises font-feature blocks', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@font-feature-values Font name 2 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.font-features.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font name 2' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('allows font-feature names to start on a different line', function () { + var lines; + lines = testGrammar.tokenizeLines("@font-feature-values\nFont name 2\n{"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font name 2' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('matches injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@font-feature-values/*{*/Font/*}*/name/*{*/2{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'name' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: '2' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises at-rules for feature names', function () { + var lines; + lines = testGrammar.tokenizeLines("@swash{ swashy: 2; }\n@ornaments{ ident: 2; }\n@annotation{ ident: 1; }\n@stylistic{ stylish: 2; }\n@styleset{ sets: 2 3 4; }\n@character-variant{ charvar: 2 }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'swash' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'swashy' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'keyword.control.at-rule.ornaments.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'keyword.control.at-rule.ornaments.css'], value: 'ornaments' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'ident' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'keyword.control.at-rule.annotation.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'keyword.control.at-rule.annotation.css'], value: 'annotation' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'ident' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'keyword.control.at-rule.stylistic.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'keyword.control.at-rule.stylistic.css'], value: 'stylistic' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'stylish' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'keyword.control.at-rule.styleset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'keyword.control.at-rule.styleset.css'], value: 'styleset' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'sets' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '3' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '4' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'keyword.control.at-rule.character-variant.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'keyword.control.at-rule.character-variant.css'], value: 'character-variant' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][4], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'charvar' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[5][7], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[5][9], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('matches feature-name rules case-insensitively', function () { + var lines; + lines = testGrammar.tokenizeLines("@sWASH{ swashy: 2; }\n@ornaMENts{ ident: 2; }\n@anNOTatION{ ident: 1; }\n@styLISTic{ stylish: 2; }\n@STYLEset{ sets: 2 3 4; }\n@CHARacter-VARiant{ charvar: 2 }"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'sWASH' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'keyword.control.at-rule.ornaments.css'], value: 'ornaMENts' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'keyword.control.at-rule.annotation.css'], value: 'anNOTatION' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'keyword.control.at-rule.stylistic.css'], value: 'styLISTic' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'keyword.control.at-rule.styleset.css'], value: 'STYLEset' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'keyword.control.at-rule.character-variant.css'], value: 'CHARacter-VARiant' }); + }); + + it('matches comments inside feature-name rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@font-feature-values Font name 2 {\n@swash{/*\n========*/swashy:/**/2;/**/}\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font name 2' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'swash' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css'], value: '========' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'swashy' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('highlights escape sequences inside feature-names', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@swash{ s\\000077a\\73hy: 1; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'swash' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 's' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css', 'constant.character.escape.codepoint.css'], value: '\\000077' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'a' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css', 'constant.character.escape.codepoint.css'], value: '\\73' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'hy' }); + }); + }); + + describe('@page', function () { + it('tokenises @page blocks correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page :first { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it.skip('tokenizes @page:right {} correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page:right{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'right' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes @page {} correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it.skip('tokenizes @page{} correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('@counter-style', function () { + it('tokenises them and their contents correctly', function () { + var lines; + lines = testGrammar.tokenizeLines("@counter-style winners-list {\n system: fixed;\n symbols: url(gold-medal.svg) url(silver-medal.svg) url(bronze-medal.svg);\n suffix: \" \";\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'winners-list' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'system' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'fixed' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'symbols' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'gold-medal.svg' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'silver-medal.svg' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'bronze-medal.svg' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'suffix' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('matches injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@counter-style/*{*/winners-list/*}*/{ system: fixed; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'winners-list' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'system' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'fixed' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("allows the counter-style's name to start on a different line", function () { + var lines; + lines = testGrammar.tokenizeLines("@counter-style\nwinners-list"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'winners-list' }); + }); + + it("highlights escape sequences inside the style's name", function () { + var tokens; + tokens = testGrammar.tokenizeLine('@counter-style A\\01F602z').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'A' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css', 'constant.character.escape.codepoint.css'], value: '\\01F602' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'z' }); + }); + }); + + describe('@document', function () { + it('correctly tokenises @document rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@document url(http://www.w3.org/),\n url-prefix(http://www.w3.org/Style/), /* Comment */\n domain(/**/mozilla.org),\n regexp(\"https:.*\") {\n body{ color: #f00; }\n }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'keyword.control.at-rule.document.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'keyword.control.at-rule.document.css'], value: 'document' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'http://www.w3.org/' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'support.function.document-rule.css'], value: 'url-prefix' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'variable.parameter.document-rule.css'], value: 'http://www.w3.org/Style/' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'comment.block.css'], value: ' Comment ' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'support.function.document-rule.css'], value: 'domain' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'variable.parameter.document-rule.css'], value: 'mozilla.org' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'support.function.document-rule.css'], value: 'regexp' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'string.quoted.double.css'], value: 'https:.*' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'punctuation.section.document.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'body' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css'], value: 'f00' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'punctuation.section.document.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('@viewport', function () { + it('tokenises @viewport blocks correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@viewport { min-width: 640px; max-width: 800px; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css'], value: 'viewport' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'min-width' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '640' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '800' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises them across lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@-O-VIEWPORT\n{\n zoom: 0.75;\n min-zoom: 0.5;\n max-zoom: 0.9;\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css'], value: '-O-VIEWPORT' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'zoom' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.75' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'min-zoom' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.5' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'max-zoom' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.9' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("@-ms-viewport/*{*/{/*\n==*/orientation: landscape;\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css'], value: '-ms-viewport' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.property-list.css', 'comment.block.css'], value: '==' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.property-list.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'orientation' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'landscape' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('unknown at-rules', function () { + it('correctly parses single-line unknown at-rules closing with semicolons', function () { + var lines; + lines = testGrammar.tokenizeLines("@foo;\n@foo ;\n@foo a;\n@foo ();\n@foo (a);"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: ' a' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: ' ()' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: ' (a)' }); + }); + + it('correctly parses single-line unknown at-rules closing with ;', function () { + var lines; + lines = testGrammar.tokenizeLines("@foo bar;\n.foo"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'foo' }); + }); + }); + }); + + describe('capitalisation', function () { + it('ignores case in at-rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@IMPoRT url(\"file.css\");\n@MEdIA (MAX-WIDTH: 2px){ }\n@pAgE :fIRST { }\n@NAMEspace \"A\";\n@foNT-FacE {}"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'IMPoRT' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'MEdIA' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'MAX-WIDTH' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'pAgE' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'fIRST' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'NAMEspace' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'keyword.control.at-rule.font-face.css'], value: 'foNT-FacE' }); + }); + + it('ignores case in property names', function () { + var lines; + lines = testGrammar.tokenizeLines("a{ COLOR: #fff; }\na{ gRId-tEMPLaTe: none; }\na{ bACkgrOUND-iMAGE: none; }\na{ -MOZ-IMAGE: none; }"); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'COLOR' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'gRId-tEMPLaTe' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'bACkgrOUND-iMAGE' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-MOZ-IMAGE' }); + }); + + it('ignores case in property keywords', function () { + var lines; + lines = testGrammar.tokenizeLines("a{ color: INItIaL; }\na{ color: trAnsPAREnT; }\na{ color: rED; }\na{ color: unSET; }\na{ color: NONe; }\na{ style: lOWER-lATIN; }\na{ color: -WebkIT-foo; }\na{ font: HelVETica; }"); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'INItIaL' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'trAnsPAREnT' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'rED' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'unSET' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'NONe' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.list-style-type.css'], value: 'lOWER-lATIN' }); + assert.deepStrictEqual(lines[6][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-WebkIT-foo' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'HelVETica' }); + }); + + it('ignores case in selectors', function () { + var lines; + lines = testGrammar.tokenizeLines("DIV:HOVER { }\n#id::BefORE { }\n#id::aFTEr { }\nTABle:nTH-cHILD(2N+1) {}\nhtML:NOT(.htiml) {}\nI::BACKDROP\nI::-mOZ-thing {}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'DIV' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'HOVER' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'BefORE' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'aFTEr' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'TABle' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nTH-cHILD' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2N+1' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'htML' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'NOT' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'I' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'BACKDROP' }); + assert.deepStrictEqual(lines[6][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: '-mOZ-thing' }); + }); + + it('ignores case in function names', function () { + var lines; + lines = testGrammar.tokenizeLines("a{ color: RGBa(); }\na{ color: hslA(); }\na{ color: URL(); }\na{ content: ATTr(); }\na{ content: CoUNTer(); }\na{ content: cuBIC-beZIER()}\na{ content: sTePs()}\na{ content: cALc(2 + 2)}"); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'RGBa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hslA' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'URL' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'ATTr' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'CoUNTer' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'cuBIC-beZIER' }); + assert.deepStrictEqual(lines[6][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'sTePs' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'support.function.calc.css'], value: 'cALc' }); + }); + + it('ignores case in unit names', function () { + var lines; + lines = testGrammar.tokenizeLines("a{width: 20EM; }\na{width: 20ReM; }\na{width: 8tURN; }\na{width: 20S; }\na{width: 20CM}\na{width: 2gRAd}"); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'EM' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.rem.css'], value: 'ReM' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'width' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.turn.css'], value: 'tURN' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.s.css'], value: 'S' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.cm.css'], value: 'CM' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.grad.css'], value: 'gRAd' }); + }); + }); + + describe('pseudo-classes', function () { + it('tokenizes regular pseudo-classes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p:first-child').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first-child' }); + }); + + it("doesn't tokenise pseudo-classes if followed by a semicolon or closed bracket", function () { + var tokens; + tokens = testGrammar.tokenizeLine('p{ left:left }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'left' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'left' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + describe(':dir()', function () { + it('tokenises :dir() and its keywords', function () { + var lines; + lines = testGrammar.tokenizeLines("a:dir(ltr ){ }\n*:dir( rtl){ }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'dir' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.selector.css', 'support.constant.text-direction.css'], value: 'ltr' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'dir' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.selector.css', 'support.constant.text-direction.css'], value: 'rtl' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('allows :dir() to include comments and newlines', function () { + var lines; + lines = testGrammar.tokenizeLines(":DIR(/**\n==*/ltr/*\n*/)"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'DIR' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: '*' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: '==' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.selector.css', 'support.constant.text-direction.css'], value: 'ltr' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe(':lang()', function () { + it('tokenizes :lang()', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':lang(zh-Hans-CN,es-419)').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'lang' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.language-range.css'], value: 'zh-Hans-CN' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'support.constant.language-range.css'], value: 'es-419' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('does not tokenize unquoted language ranges containing asterisks', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':lang(zh-*-CN)').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css'], value: 'zh-*-CN' }); + }); + + it('tokenizes language ranges containing asterisks quoted as strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':lang("zh-*-CN",\'*-ab-\')').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.double.css', 'support.constant.language-range.css'], value: 'zh-*-CN' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.single.css', 'support.constant.language-range.css'], value: '*-ab-' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + }); + }); + + describe(':not()', function () { + it('tokenises other selectors inside :not()', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*:not(.class-name):not(div) {}').tokens; + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'class-name' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'div' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*:not(/*(*/.class-name/*)*/):not(/*b*/) {}').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'class-name' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: ')' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: 'b' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe(':nth-*()', function () { + it('tokenizes :nth-child()', function () { + var tokens; + tokens = testGrammar.tokenizeLines(':nth-child(2n+1)\n:nth-child(2n -1)\n:nth-child(-2n+ 1)\n:nth-child(-2n - 1)\n:nth-child(odd)\n:nth-child(even)\n:nth-child( odd )\n:nth-child( even )'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-child' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n+1' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n -1' }); + assert.deepStrictEqual(tokens[2][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-2n+ 1' }); + assert.deepStrictEqual(tokens[3][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-2n - 1' }); + assert.deepStrictEqual(tokens[4][3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'odd' }); + assert.deepStrictEqual(tokens[5][3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'even' }); + assert.deepStrictEqual(tokens[6][3], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[6][4], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'odd' }); + assert.deepStrictEqual(tokens[7][4], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'even' }); + assert.deepStrictEqual(tokens[7][5], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + }); + + it('tokenizes :nth-last-child()', function () { + var tokens; + tokens = testGrammar.tokenizeLines(':nth-last-child(2n)\n:nth-last-child( -2n)\n:nth-last-child( 2n )\n:nth-last-child(even)'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-last-child' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-2n' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n' }); + assert.deepStrictEqual(tokens[2][6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[3][3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'even' }); + }); + + it('tokenizes :nth-of-type()', function () { + var tokens; + tokens = testGrammar.tokenizeLines('img:nth-of-type(+n+1)\nimg:nth-of-type(-n+1)\nimg:nth-of-type(n+1)'); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-of-type' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '+n+1' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-n+1' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: 'n+1' }); + }); + + it('tokenizes ::nth-last-of-type()', function () { + var tokens; + tokens = testGrammar.tokenizeLines('h1:nth-last-of-type(-1)\nh1:nth-last-of-type(+2)\nh1:nth-last-of-type(3)'); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-last-of-type' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-1' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '+2' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '3' }); + }); + }); + }); + + describe('pseudo-elements', function () { + it('tokenizes both : and :: notations for pseudo-elements introduced in CSS 1 and 2', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.opening:first-letter').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'opening' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'first-letter' }); + + tokens = testGrammar.tokenizeLine('q::after').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'q' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'after' }); + }); + + it('tokenizes both : and :: notations for vendor-prefixed pseudo-elements', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':-ms-input-placeholder').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: '-ms-input-placeholder' }); + + tokens = testGrammar.tokenizeLine('::-webkit-input-placeholder').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: '-webkit-input-placeholder' }); + }); + + it('only tokenizes the :: notation for other pseudo-elements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('::selection').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'selection' }); + + tokens = testGrammar.tokenizeLine(':selection').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: ':selection' }); + }); + }); + describe('compound selectors', function () { + it('tokenizes the combination of type selectors followed by class selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom.class').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'class' }); + }); + + it('tokenizes the combination of type selectors followed by pseudo-classes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom:hover').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'hover' }); + }); + + it('tokenizes the combination of type selectors followed by pseudo-elements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom::shadow').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'shadow' }); + }); + }); + }); + + describe('property lists (declaration blocks)', function () { + it('tokenizes inline property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { font-size: inherit; }').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'font-size' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes compact inline property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div{color:inherit;float:left}').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'float' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'left' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes multiple inline property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLines('very-custom { color: inherit }\nanother-one { display : none ; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[1][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'another-one' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[1][5], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[1][6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'none' }); + assert.deepStrictEqual(tokens[1][9], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[1][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[1][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes custom properties', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':root { --white: #FFF; }').tokens; + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'variable.css'], value: '--white' }); + }); + + it('tokenises commas between property values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ text-shadow: a, b; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + }); + + it('tokenises superfluous semicolons', function () { + var i, j, lines; + lines = testGrammar.tokenizeLines('.test{ width: 20em;;;;;;;;;\n;;;;;;;;;height: 10em; }'); + for (i = j = 0; j <= 8; i = ++j) { + assert.deepStrictEqual(lines[0][i + 9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[1][i], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + } + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'height' }); + }); + + describe('values', function () { + it('tokenizes color keywords', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#jon { color: snow; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-extended-color-name.css'], value: 'snow' }); + }); + + it('tokenises RGBA values in hex notation', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p{ color: #f030; }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css'], value: 'f030' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{ color: #CAFEBABE; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css'], value: 'CAFEBABE' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{ color: #CAFEBABEF; }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: '#CAFEBABEF' }); + }); + + it('tokenizes common font names', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p { font-family: Verdana, Helvetica, sans-serif; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'Verdana' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'Helvetica' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'sans-serif' }); + }); + + it('tokenizes predefined list style types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('ol.myth { list-style-type: cjk-earthly-branch }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.list-style-type.css'], value: 'cjk-earthly-branch' }); + }); + + it('tokenizes numeric values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { font-size: 14px; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '14' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + }); + + it('does not tokenize invalid numeric values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { font-size: test14px; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'test14px' }); + + tokens = testGrammar.tokenizeLine('div { font-size: test-14px; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'test-14px' }); + }); + + it('tokenizes vendor-prefixed values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.edge { cursor: -webkit-zoom-in; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-webkit-zoom-in' }); + + tokens = testGrammar.tokenizeLine('.edge { width: -moz-min-content; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-moz-min-content' }); + + tokens = testGrammar.tokenizeLine('.edge { display: -ms-grid; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-ms-grid' }); + }); + + it('tokenizes custom variables', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { color: var(--primary-color) }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--primary-color' }); + }); + + it('tokenises numeric values correctly', function () { + var lines; + lines = testGrammar.tokenizeLines(".a { a: 12em }\n.a { a: 4.01ex }\n.a { a: -456.8ch }\n.a { a: 0.0REM }\n.a { a: +0.0vh }\n.a { a: -0.0vw }\n.a { a: .6px }\n.a { a: 10e3mm }\n.a { a: 10E3cm }\n.a { a: -3.4e+2In }\n.a { a: -3.4e-2ch }\n.a { a: +.5E-2% }\n.a { a: -3.4e-2% }"); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '12' }); + assert.deepStrictEqual(lines[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '4.01' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.ex.css'], value: 'ex' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-456.8' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.ch.css'], value: 'ch' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.0' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.rem.css'], value: 'REM' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '+0.0' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.vh.css'], value: 'vh' }); + assert.deepStrictEqual(lines[5][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-0.0' }); + assert.deepStrictEqual(lines[5][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.vw.css'], value: 'vw' }); + assert.deepStrictEqual(lines[6][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '.6' }); + assert.deepStrictEqual(lines[6][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[7][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '10e3' }); + assert.deepStrictEqual(lines[7][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.mm.css'], value: 'mm' }); + assert.deepStrictEqual(lines[8][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '10E3' }); + assert.deepStrictEqual(lines[8][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.cm.css'], value: 'cm' }); + assert.deepStrictEqual(lines[9][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-3.4e+2' }); + assert.deepStrictEqual(lines[9][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.in.css'], value: 'In' }); + assert.deepStrictEqual(lines[10][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-3.4e-2' }); + assert.deepStrictEqual(lines[10][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.ch.css'], value: 'ch' }); + assert.deepStrictEqual(lines[11][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '+.5E-2' }); + assert.deepStrictEqual(lines[11][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[12][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-3.4e-2' }); + assert.deepStrictEqual(lines[12][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + }); + + describe('functional notation', function () { + describe('attr()', function () { + it('tokenises parameters correctly and case-insensitively', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{content:aTTr(data-width px, inherit)}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'aTTr' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'data-width' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'px' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('matches variables', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{content:ATTR(VAR(--name) px, "N/A")}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'ATTR' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'VAR' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--name' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'px' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'N/A' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe('calc()', function () { + it('tokenises calculations', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n width: calc(3px + -1em);\n width: calc(3px - -1em);\n width: calc(3px * 2);\n width: calc(3px / 2);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'support.function.calc.css'], value: 'calc' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '3' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '+' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '-1' }); + assert.deepStrictEqual(lines[1][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '-' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '-1' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '*' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '2' }); + }); + + it('requires whitespace around + and - operators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ width: calc(3px+1em); }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css'], value: '+' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + + tokens = testGrammar.tokenizeLine('a{ width: calc(3px--1em); height: calc(10-1em);}').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css'], value: '--1em' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '10' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css'], value: '-1em' }); + }); + + it('does not require whitespace around * and / operators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ width: calc(3px*2); }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '*' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '2' }); + + tokens = testGrammar.tokenizeLine('a{ width: calc(3px/2); }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '2' }); + }); + + it('matches variable expansions inside calculations', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.foo { margin-top: calc(var(--gap) + 1px); }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'support.function.calc.css'], value: 'calc' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--gap' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '+' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('colours', function () { + it('tokenises colour functions correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: rgb(187,255,221); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgb' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '187' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '255' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '221' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('a{ color: RGBa( 100%, 0% ,20.17% ,.5 ); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'RGBa' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '20.17' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.5' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('a{color:HSL(0, 00100%,50%)}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'HSL' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '00100' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('a{color:HSLa(2,.0%,1%,.7)}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'HSLa' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.0' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.7' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('matches variables as colour components', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: RGBA(var(--red), 0% , 20%, .2)}').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--red' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + }); + + it('matches comments between colour components', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: rgba(/**/255/*=*/,0,/*2.2%*/51/*,*/0.2)}').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '255' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '51' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ',' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0.2' }); + }); + + it('allows colour components to be split across lines', function () { + var lines; + lines = testGrammar.tokenizeLines(".frost{\n background-color: rgba(\n var(--red), /* Red */\n var(--green), /* Green */\n var(--blue), /* Blue */\n /* var(--test),\n /**/var(--opacity) /* Transparency */\n );\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--red' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Red ' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--green' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Green ' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--blue' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Blue ' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' var(--test),' }); + assert.deepStrictEqual(lines[6][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' /*' }); + assert.deepStrictEqual(lines[6][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[6][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[6][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[6][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--opacity' }); + assert.deepStrictEqual(lines[6][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[6][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[6][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Transparency ' }); + assert.deepStrictEqual(lines[6][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[7][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe('gradients', function () { + it('tokenises linear gradients', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ background-image: linear-gradient( 45deg, blue, red ); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'linear-gradient' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.deg.css'], value: 'deg' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'blue' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'red' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('a{ background-image: LINear-graDIEnt( ellipse to left top, blue, red);').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'LINear-graDIEnt' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'ellipse' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'to' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'left' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'top' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'blue' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'red' }); + }); + + it('tokenises radial gradients', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ background-image: radial-gradient(farthest-corner at 45px 45px , #f00 0%, #00f 100%);}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'radial-gradient' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'farthest-corner' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'at' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'f00' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + + tokens = testGrammar.tokenizeLine('a{ background-image: RADial-gradiENT(16px at 60px 50%,#000 0%, #000 14px, rgba(0,0,0,.3) 18px, transparent 19px)}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'RADial-gradiENT' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '16' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'at' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '60' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: '000' }); + assert.deepStrictEqual(tokens[33], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(tokens[34], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[35], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[36], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[41], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.3' }); + assert.deepStrictEqual(tokens[42], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[48], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'transparent' }); + }); + + it('matches gradients that span multiple lines with injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n background-image: raDIAL-gradiENT(\n ellipse farthest-corner/*@*/at/*@*/470px 47px,/*===\n========*/#FFFF80 20%, rgba(204, 153, 153, 0.4) 30%,/*))))))))}*/#E6E6FF 60%); }"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'raDIAL-gradiENT' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'ellipse' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'farthest-corner' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '@' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'at' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '470' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '===' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '========' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'FFFF80' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0.4' }); + assert.deepStrictEqual(lines[3][21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][26], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][27], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '))))))))}' }); + assert.deepStrictEqual(lines[3][28], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][29], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[3][30], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'E6E6FF' }); + }); + + it('highlights vendored gradient functions', function () { + var lines; + lines = testGrammar.tokenizeLines(".grad {\n background-image: -webkit-linear-gradient(top, /* For Chrome 25 and Safari 6, iOS 6.1, Android 4.3 */ hsl(0, 80%, 70%), #bada55);\n background-image: -moz-linear-gradient(top, /* For Firefox (3.6 to 15) */ hsl(0, 80%, 70%), #bada55);\n background-image: -o-linear-gradient(top, /* For old Opera (11.1 to 12.0) */ hsl(0, 80%, 70%), #bada55);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: '-webkit-linear-gradient' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'top' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: ' For Chrome 25 and Safari 6, iOS 6.1, Android 4.3 ' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hsl' }); + assert.deepStrictEqual(lines[1][22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '70' }); + assert.deepStrictEqual(lines[1][23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[1][24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][27], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[1][28], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'bada55' }); + assert.deepStrictEqual(lines[1][29], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: '-moz-linear-gradient' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'top' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: ' For Firefox (3.6 to 15) ' }); + assert.deepStrictEqual(lines[2][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hsl' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][29], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: '-o-linear-gradient' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: ' For old Opera (11.1 to 12.0) ' }); + assert.deepStrictEqual(lines[3][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hsl' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + }); + + it('highlights antique Webkit syntax as deprecated', function () { + var lines; + lines = testGrammar.tokenizeLines(".grad {\n background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,\n from( rgb(0, 171, 235)),\n color-stop(0.5, rgb(255, 255, 255)),\n color-stop(0.5, rgb(102, 204, 0)),\n to(rgb(255, 255, 255))),\n -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)),\n -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)),\n -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)),\n -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700));\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'support.constant.property-value.css'], value: 'linear' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[1][20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'from' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgb' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '171' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'color-stop' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '0.5' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'color-stop' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '0.5' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgb' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '102' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '204' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[4][15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'to' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[5][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[6][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[6][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[6][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'support.constant.property-value.css'], value: 'radial' }); + assert.deepStrictEqual(lines[6][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[6][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(lines[6][31], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[7][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[7][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[9][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[9][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[9][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'support.constant.property-value.css'], value: 'radial' }); + assert.deepStrictEqual(lines[9][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[9][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[9][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '150' }); + assert.deepStrictEqual(lines[9][54], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[9][55], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[10][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('other functions', function () { + it('tokenises basic-shape functions', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n shape-outside: circle(20em/*=*/at 50% 50%);\n shape-outside: inset(1em, 1em, 1em, 1em);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'support.function.shape.css'], value: 'circle' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'keyword.operator.shape.css'], value: 'at' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[1][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[1][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[1][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[1][18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'support.function.shape.css'], value: 'inset' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + it('tokenises OpenType feature functions', function () { + var lines; + lines = testGrammar.tokenizeLines(".font{\n font-variant-alternates: stylistic(user-defined-ident);\n font-variant-alternates: styleset(user-defined-ident);\n font-variant-alternates: character-variant(user-defined-ident);\n font-variant-alternates: swash(user-defined-ident);\n font-variant-alternates: ornaments(user-defined-ident);\n font-variant-alternates: annotation(user-defined-ident);\n font-variant-alternates: swash(ident1) annotation(ident2);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'stylistic' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'styleset' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'character-variant' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'swash' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'ornaments' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[5][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[6][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'annotation' }); + assert.deepStrictEqual(lines[6][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[6][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[6][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[7][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'swash' }); + assert.deepStrictEqual(lines[7][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'ident1' }); + assert.deepStrictEqual(lines[7][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[7][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'annotation' }); + assert.deepStrictEqual(lines[7][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[7][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'ident2' }); + assert.deepStrictEqual(lines[7][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises image-set()', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n background-image: image-set( \"foo.png\" 1x,\n \"foo-2x.png\" 2x,\n \"foo-print.png\" 600dpi );\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'background-image' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'image-set' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'foo.png' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.other.density.css'], value: '1x' }); + assert.deepStrictEqual(lines[1][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'foo-2x.png' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.other.density.css'], value: '2x' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'foo-print.png' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.css'], value: '600' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.css', 'keyword.other.unit.dpi.css'], value: 'dpi' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('timing-functions', function () { + it('tokenises them correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ zoom: cubic-bezier(/**/1.2,/*=*/0,0,0/**/)}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'cubic-bezier' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '1.2' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('highlights the "start" and "end" keywords', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ before: steps(0, start); after: steps(1, end); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'steps' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.constant.step-direction.css'], value: 'start' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.constant.step-direction.css'], value: 'end' }); + }); + }); + + describe('variables', function () { + it('scopes var() statements as variables', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{color: var(--name)}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--name' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{color: var( --name )}').tokens; + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--name' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('allows injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: var( /*=*/ --something ) }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--something' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises fallback values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.bar{ width: var(--page-width, /*;;;);*/ 2); }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--page-width' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css'], value: ';;;);' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + + it('does not tokenise functions with whitespace between name and parameters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ p: attr (title); }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css'], value: 'p' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'attr (title' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{url:url (s)}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css'], value: 'url' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'url (s' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{content:url ("http://github.com/");}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'url (' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'http://github.com/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{content: url (http://a.pl/)}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'url (http://a.pl/' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{ color: rgb (187,255,221); }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'rgb (' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '187' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '255' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '221' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + describe('Unicode ranges', function () { + it('tokenises single codepoints', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ a: U+A5 }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+A5' }); + }); + + it('tokenises codepoint ranges', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ a: U+0025-00FF }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+0025' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css', 'punctuation.separator.dash.unicode-range.css'], value: '-' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: '00FF' }); + + tokens = testGrammar.tokenizeLine('a{ unicode-range: u+0-7F }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'u+0' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css', 'punctuation.separator.dash.unicode-range.css'], value: '-' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: '7F' }); + }); + + it('tokenises wildcard ranges', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ unicode-range: U+4?? }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+4??' }); + + tokens = testGrammar.tokenizeLine('a{ unicode-range: U+0025-00FF, U+4?? }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+0025' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css', 'punctuation.separator.dash.unicode-range.css'], value: '-' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: '00FF' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+4??' }); + }); + }); + }); + }); + + describe('escape sequences', function () { + it('tokenizes escape sequences in single-quoted strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine("very-custom { content: '\\c0ffee' }").tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.codepoint.css'], value: '\\c0ffee' }); + }); + + it('tokenizes escape sequences in double-quoted strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom { content: "\\c0ffee" }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.codepoint.css'], value: '\\c0ffee' }); + }); + + it('tokenises escape sequences in selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('\\61 \\{ { } \\}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'constant.character.escape.codepoint.css'], value: '\\61' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\}' }); + + tokens = testGrammar.tokenizeLine('\\61\\ \\. \\@media {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'constant.character.escape.codepoint.css'], value: '\\61' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\.' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\@' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css'], value: 'media' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises escape sequences in property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a { \\77\\69\\64\\74\\68: 20px; }').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\77' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\69' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\64' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\74' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\68' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + }); + + it('tokenises escape sequences in property values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a { content: \\1F764; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.character.escape.codepoint.css'], value: '\\1F764' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('unclosed strings', function () { + it('highlights an unterminated string as an error', function () { + var tokens; + tokens = testGrammar.tokenizeLine("a{ content: 'aaaa").tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css'], value: 'aaaa' }); + + tokens = testGrammar.tokenizeLine('a{ content: "aaaa').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aaaa' }); + }); + + it.skip("knows when a string is line-wrapped - a", function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n content: \"aaaaa\\\\\\\naaa\"; color: red;\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aaaaa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.css'], value: '\\\\' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.newline.css'], value: '\\' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aaa' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + }); + + it.skip("knows when a string is line-wrapped - b", function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n content: 'aaaaa\\\\\\\naaa'; color: red;\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css'], value: 'aaaaa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.css'], value: '\\\\' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.newline.css'], value: '\\' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css'], value: 'aaa' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + }); + + it('highlights escape sequences inside invalid strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ content: "aaa\\"aa').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aaa' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css', 'constant.character.escape.css'], value: '\\"' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aa' }); + + tokens = testGrammar.tokenizeLine("a{ content: 'aaa\\'aa").tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css'], value: 'aaa' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css', 'constant.character.escape.css'], value: "\\'" }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css'], value: 'aa' }); + }); + + it.skip('highlights unclosed lines in line-wrapped strings', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n content: \"aaa\\\"aa\\\naaaa\naaaa; color: red;\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aaa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.css'], value: '\\"' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aa' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.newline.css'], value: '\\' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aaaa' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'aaaa' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'red' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('comments', function () { + it('tokenises comments inside @import statements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import /* url("name"); */ "1.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url("name"); ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: '1.css' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import/*";"*/ url("2.css");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: '";"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: '2.css' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import url("3.css") print /* url(";"); */;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: '3.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url(";"); ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('tokenises comments inside @font-face statements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@font-face/*"{;}"*/{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'keyword.control.at-rule.font-face.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'keyword.control.at-rule.font-face.css'], value: 'font-face' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'comment.block.css'], value: '"{;}"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes comments before media queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('/* comment */ @media').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'comment.block.css'], value: ' comment ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + }); + + it('tokenizes comments after media queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media/* comment */ ()').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' comment ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + }); + + it('tokenizes comments inside query lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (max-height: 40em/* comment */)').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-height' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '40' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' comment ' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + }); + + it('tokenizes inline comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('section {border:4px/*padding:1px*/}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'section' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'border' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '4' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css'], value: 'padding:1px' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes multi-line comments', function () { + var lines; + lines = testGrammar.tokenizeLines(" section {\n border:4px /*1px;\n padding:1px*/\n}"); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css'], value: '1px;' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css'], value: ' padding:1px' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('Animations', function () { + it('does not confuse animation names with predefined keywords', function () { + var tokens; + tokens = testGrammar.tokenizeLines('.animated {\n animation-name: orphan-black;\n animation-name: line-scale;\n}'); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'orphan-black' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'line-scale' }); + }); + }); + + describe('Transforms', function () { + it('tokenizes transform functions', function () { + var tokens; + tokens = testGrammar.tokenizeLines('.transformed {\n transform: matrix(0, 1.5, -1.5, 0, 0, 100px);\n transform: rotate(90deg) translateX(100px) scale(1.5);\n}'); + assert.deepStrictEqual(tokens[1][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'transform' }); + assert.deepStrictEqual(tokens[1][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'matrix' }); + assert.deepStrictEqual(tokens[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[1][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-1.5' }); + assert.deepStrictEqual(tokens[1][22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[1][23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'rotate' }); + assert.deepStrictEqual(tokens[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'translateX' }); + assert.deepStrictEqual(tokens[2][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'scale' }); + }); + }); + + describe("performance regressions", function () { + it("does not hang when tokenizing invalid input preceding an equals sign", function () { + var start; + start = Date.now(); + testGrammar.tokenizeLine('\n [}~" + ('ÁÂÃÄÅÆÇÈÊËÍÎ'.repeat(100)) + "\n"); + assert.ok(Date.now() - start < 5000); + }); + }); + + // Skipped because `firstLineRegex.scanner` does not exist in `vscode-textmate` + describe.skip("firstLineMatch", function () { + it("recognises Emacs modelines", function () { + var invalid, j, k, len, len1, line, ref, ref1, valid; + valid = "#-*- CSS -*-\n#-*- mode: CSS -*-\n/* -*-css-*- */\n// -*- CSS -*-\n/* -*- mode:CSS -*- */\n// -*- font:bar;mode:CSS -*-\n// -*- font:bar;mode:CSS;foo:bar; -*-\n// -*-font:mode;mode:CSS-*-\n// -*- foo:bar mode: css bar:baz -*-\n\" -*-foo:bar;mode:css;bar:foo-*- \";\n\" -*-font-mode:foo;mode:css;foo-bar:quux-*-\"\n\"-*-font:x;foo:bar; mode : CsS; bar:foo;foooooo:baaaaar;fo:ba;-*-\";\n\"-*- font:x;foo : bar ; mode : cSS ; bar : foo ; foooooo:baaaaar;fo:ba-*-\";"; + ref = valid.split(/\n/); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + assert.notEqual(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null); + } + invalid = "/* --*css-*- */\n/* -*-- CSS -*-\n/* -*- -- CSS -*-\n/* -*- CSS -;- -*-\n// -*- CCSS -*-\n// -*- CSS; -*-\n// -*- css-stuff -*-\n/* -*- model:css -*-\n/* -*- indent-mode:css -*-\n// -*- font:mode;CSS -*-\n// -*- mode: -*- CSS\n// -*- mode: I-miss-plain-old-css -*-\n// -*-font:mode;mode:css--*-"; + ref1 = invalid.split(/\n/); + for (k = 0, len1 = ref1.length; k < len1; k++) { + line = ref1[k]; + assert.equal(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null) + } + }); + + it("recognises Vim modelines", function () { + var invalid, j, k, len, len1, line, ref, ref1, valid; + valid = "vim: se filetype=css:\n# vim: se ft=css:\n# vim: set ft=CSS:\n# vim: set filetype=CSS:\n# vim: ft=CSS\n# vim: syntax=CSS\n# vim: se syntax=css:\n# ex: syntax=CSS\n# vim:ft=css\n# vim600: ft=css\n# vim>600: set ft=css:\n# vi:noai:sw=3 ts=6 ft=CSS\n# vi::::::::::noai:::::::::::: ft=CSS\n# vim:ts=4:sts=4:sw=4:noexpandtab:ft=cSS\n# vi:: noai : : : : sw =3 ts =6 ft =Css\n# vim: ts=4: pi sts=4: ft=CSS: noexpandtab: sw=4:\n# vim: ts=4 sts=4: ft=css noexpandtab:\n# vim:noexpandtab sts=4 ft=css ts=4\n# vim:noexpandtab:ft=css\n# vim:ts=4:sts=4 ft=css:noexpandtab:\x20\n# vim:noexpandtab titlestring=hi\|there\\\\ ft=css ts=4"; + ref = valid.split(/\n/); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + assert.notEqual(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null); + } + invalid = "ex: se filetype=css:\n_vi: se filetype=CSS:\n vi: se filetype=CSS\n# vim set ft=css3\n# vim: soft=css\n# vim: clean-syntax=css:\n# vim set ft=css:\n# vim: setft=CSS:\n# vim: se ft=css backupdir=tmp\n# vim: set ft=css set cmdheight=1\n# vim:noexpandtab sts:4 ft:CSS ts:4\n# vim:noexpandtab titlestring=hi\\|there\\ ft=CSS ts=4\n# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=CSS ts=4"; + ref1 = invalid.split(/\n/); + for (k = 0, len1 = ref1.length; k < len1; k++) { + line = ref1[k]; + assert.equal(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null); + } + }); + }); + + describe("Missing supported properties regressions", function () { + it("recognises place-items property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { place-items: center center; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'place-items' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("recognises place-self property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { place-self: center center; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'place-self' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("recognises place-content property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { place-content: center center; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'place-content' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("recognises row-gap property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { row-gap: 5px; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'row-gap' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '5' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); +}); diff --git a/src/vscode-css/testing-util/test.mjs b/src/vscode-css/testing-util/test.mjs new file mode 100644 index 0000000..26abebf --- /dev/null +++ b/src/vscode-css/testing-util/test.mjs @@ -0,0 +1,69 @@ +// from https://github.com/microsoft/vscode-textmate/blob/677f741f5b5ef69589e0e5d2a6e556f94514cbfd/README.md +import fs from 'fs'; +import path from 'path'; +import vsctm from 'vscode-textmate'; +import oniguruma from 'vscode-oniguruma'; +import cson from 'cson'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const wasmBin = fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'vscode-oniguruma', 'release', 'onig.wasm')).buffer; +const vscodeOnigurumaLib = oniguruma.loadWASM(wasmBin).then(() => { + return { + createOnigScanner(patterns) { return new oniguruma.OnigScanner(patterns); }, + createOnigString(s) { return new oniguruma.OnigString(s); } + }; +}); + +// Create a registry that can create a grammar from a scope name. +const registry = new vsctm.Registry({ + onigLib: vscodeOnigurumaLib, + loadGrammar: async (scopeName) => { + if (scopeName === 'source.css') { + const file = await fs.promises.readFile(path.join(__dirname, '..', 'grammars', 'css.cson')); + return vsctm.parseRawGrammar(cson.createJSONString(cson.parse(file.toString())), 'css.json'); + } + console.log(`Unknown scope name: ${scopeName}`); + return null; + } +}); + +const grammar = await registry.loadGrammar('source.css'); + +export default { + grammar: grammar, + tokenizeLine: function tokenizeLine(line) { + const lineTokens = grammar.tokenizeLine(line, vsctm.INITIAL); + + lineTokens.tokens.forEach((token) => { + token.value = line.slice(token.startIndex, token.endIndex); + delete token.startIndex; + delete token.endIndex; + }); + + return lineTokens; + }, + tokenizeLines: function tokenizeLines(text) { + const lines = text.split(/\r\n|\r|\n/g); + const tokenizedLines = []; + + let ruleStack = vsctm.INITIAL; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineTokens = grammar.tokenizeLine(line, ruleStack); + + lineTokens.tokens.forEach((token) => { + token.value = line.slice(token.startIndex, token.endIndex); + delete token.startIndex; + delete token.endIndex; + }); + + tokenizedLines.push(lineTokens.tokens); + ruleStack = lineTokens.ruleStack; + } + + return tokenizedLines; + } +} diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index e93902e..b03978e 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1,2163 +1,1943 @@ { - "information_for_contributors": [ - "This file has been converted from https://github.com/microsoft/vscode-css/blob/master/grammars/css.cson", - "If you want to provide a fix or improvement, please create a pull request against the original repository.", - "Once accepted there, we are happy to receive an update request." - ], - "version": "https://github.com/microsoft/vscode-css/commit/c216f777497265700ff336f739328e5197e012cd", - "name": "CSS", - "scopeName": "source.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#combinators" - }, - { - "include": "#selector" - }, - { - "include": "#at-rules" - }, - { - "include": "#rule-list" - }, - { - "include": "#nesting-selector" - } - ], - "repository": { - "arithmetic-operators": { - "match": "[*/]|(?<=\\s|^)[-+](?=\\s|$)", - "name": "keyword.operator.arithmetic.css" - }, - "at-rules": { - "patterns": [ - { - "begin": "(?i)(?=@container(\\s|\\(|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)container", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.container.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*[{;])", - "name": "meta.at-rule.container.header.css", - "patterns": [ - { - "include": "#container-condition" - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.container.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.container.end.bracket.curly.css" - } - }, - "name": "meta.at-rule.container.body.css", - "patterns": [ - { - "include": "#nesting-at-rules" - }, - { - "include": "$self" - } - ] - } - ] - }, - { - "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))", - "end": ";|(?=$)", - "endCaptures": { - "0": { - "name": "punctuation.terminator.rule.css" - } - }, - "name": "meta.at-rule.charset.css", - "patterns": [ - { - "captures": { - "1": { - "name": "invalid.illegal.not-lowercase.charset.css" - }, - "2": { - "name": "invalid.illegal.leading-whitespace.charset.css" - }, - "3": { - "name": "invalid.illegal.no-whitespace.charset.css" - }, - "4": { - "name": "invalid.illegal.whitespace.charset.css" - }, - "5": { - "name": "invalid.illegal.not-double-quoted.charset.css" - }, - "6": { - "name": "invalid.illegal.unclosed-string.charset.css" - }, - "7": { - "name": "invalid.illegal.unexpected-characters.charset.css" - } - }, - "match": "(?x) # Possible errors:\n\\G\n((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive)\n|\n\\G(\\s+) # Preceding whitespace\n|\n(@charset\\S[^;]*) # No whitespace after @charset\n|\n(?<=@charset) # Before quoted charset name\n(\\x20{2,}|\\t+) # More than one space used, or a tab\n|\n(?<=@charset\\x20) # Beginning of charset name\n([^\";]+) # Not double-quoted\n|\n(\"[^\"]+$) # Unclosed quote\n|\n(?<=\") # After charset name\n([^;]+) # Unexpected junk instead of semicolon" - }, - { - "captures": { - "1": { - "name": "keyword.control.at-rule.charset.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "match": "((@)charset)(?=\\s)" - }, - { - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.css" - } - }, - "end": "\"|$", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.css" - } - }, - "name": "string.quoted.double.css", - "patterns": [ - { - "begin": "(?:\\G|^)(?=(?:[^\"])+$)", - "end": "$", - "name": "invalid.illegal.unclosed.string.css" - } - ] - } - ] - }, - { - "begin": "(?i)((@)import)(?:\\s+|$|(?=['\"]|/\\*))", - "beginCaptures": { - "1": { - "name": "keyword.control.at-rule.import.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": ";", - "endCaptures": { - "0": { - "name": "punctuation.terminator.rule.css" - } - }, - "name": "meta.at-rule.import.css", - "patterns": [ - { - "begin": "\\G\\s*(?=/\\*)", - "end": "(?<=\\*/)\\s*", - "patterns": [ - { - "include": "#comment-block" - } - ] - }, - { - "include": "#string" - }, - { - "include": "#url" - }, - { - "include": "#media-query-list" - } - ] - }, - { - "begin": "(?i)((@)font-face)(?=\\s*|{|/\\*|$)", - "beginCaptures": { - "1": { - "name": "keyword.control.at-rule.font-face.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?!\\G)", - "name": "meta.at-rule.font-face.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#rule-list" - } - ] - }, - { - "begin": "(?i)(@)page(?=[\\s:{]|/\\*|$)", - "captures": { - "0": { - "name": "keyword.control.at-rule.page.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*($|[:{;]))", - "name": "meta.at-rule.page.css", - "patterns": [ - { - "include": "#rule-list" - } - ] - }, - { - "begin": "(?i)(?=@media(\\s|\\(|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)media", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.media.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*[{;])", - "name": "meta.at-rule.media.header.css", - "patterns": [ - { - "include": "#media-query-list" - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.media.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.media.end.bracket.curly.css" - } - }, - "name": "meta.at-rule.media.body.css", - "patterns": [ - { - "include": "#nesting-at-rules" - }, - { - "include": "$self" - } - ] - } - ] - }, - { - "begin": "(?i)(?=@counter-style([\\s'\"{;]|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)counter-style", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.counter-style.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*{)", - "name": "meta.at-rule.counter-style.header.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "captures": { - "0": { - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n(?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter\n(?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n |\\\\(?:[0-9a-fA-F]{1,6}|.)\n)*", - "name": "variable.parameter.style-name.css" - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.property-list.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.property-list.end.bracket.curly.css" - } - }, - "name": "meta.at-rule.counter-style.body.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#rule-list-innards" - } - ] - } - ] - }, - { - "begin": "(?i)(?=@document([\\s'\"{;]|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)document", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.document.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*[{;])", - "name": "meta.at-rule.document.header.css", - "patterns": [ - { - "begin": "(?i)(?>>", - "name": "invalid.deprecated.combinator.css" - }, - { - "match": ">>|>|\\+|~", - "name": "keyword.operator.combinator.css" - } - ] - }, - "commas": { - "match": ",", - "name": "punctuation.separator.list.comma.css" - }, - "comment-block": { - "begin": "/\\*", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.begin.css" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.end.css" - } - }, - "name": "comment.block.css" - }, - "container-condition": { - "begin": "\\G", - "end": "(?=\\s*[{;])", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "match": "(?xi)\n (?<=not.*)not\n # Only one `not` in allowed", - "name": "invalid.illegal.multiple-not.container.css" - }, - { - "match": "(?i)(?<=\\s|^|,|\\*/)(and|or|not)(?=\\s|{|/\\*|$)", - "name": "keyword.operator.logical.$1.container.css" - }, - { - "include": "#container-name" - }, - { - "include": "#container-query" - } - ] - }, - "container-name": { - "begin": "\\G", - "end": "(?xi)\n (?=(?:\\(|not)|style\\()\n # Ends when `(`, `not`, or `style(` is reached", - "patterns": [ - { - "include": "#comment-block" - }, - { - "captures": { - "1": { - "name": "invalid.illegal.constant.container-name.container.css" - }, - "2": { - "name": "support.constant.container-name.container.css" - } - }, - "match": "(?xi)\n (?<=^|\\s|\\*/)\n(?:\n # Invalid name\n (default|none)\n |\n # Valid names\n ([a-zA-Z0-9\\-_\\\\]+)\n )\n(?=$|[({\\s;]|/\\*)" - } - ] - }, - "container-query": { - "begin": "((?<=\\s)\\()|(style)(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.parameters.begin.bracket.round.css" - }, - "2": { - "name": "support.style-query.container.css" - }, - "3": { - "name": "punctuation.definition.parameters.begin.bracket.round.css" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.css" - } - }, - "patterns": [ - { - "begin": "(?xi)\n (?<=\\s\\()\n # Rules for size ", - "end": "(?=\\))", - "patterns": [ - { - "include": "#container-query-features" - }, - { - "include": "#container-size-features" - }, - { - "include": "#container-size-feature-keywords" - } - ] - }, - { - "begin": "(?xi)\n (?<=style\\()\n # Rules for container-query ", - "end": "(?=\\))", - "name": "style-query.container.css", - "patterns": [ - { - "include": "#container-query-features" - }, - { - "include": "#container-style-features" - }, - { - "include": "#container-style-feature-keywords" - } - ] - } - ] - }, - "container-query-features": { - "patterns": [ - { - "match": ":", - "name": "punctuation.separator.key-value.css" - }, - { - "match": ">=|<=|=|<|>", - "name": "keyword.operator.comparison.css" - }, - { - "include": "#numeric-values" - }, - { - "include": "#comment-block" - } - ] - }, - "container-size-features": { - "match": "(?xi)\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?:\n # Standardised features\n (\n (?:min-|max-)? # Range features\n (?: aspect-ratio\n | block-size\n | height\n | inline-size\n | width\n )\n | orientation\n )\n )\n (?=\\s|$|[><:=]|\\)|/\\*) # Terminates cleanly", - "name": "support.size.property-name.container.css" - }, - "container-size-feature-keywords": { - "patterns": [ - { - "match": "(?xi)\n (?<=^|\\s|:|\\*/)\n # Preceded by whitespace, colon, or comment\n (?: portrait\n | landscape\n # Orientation\n )\n (?=\\s|\\)|$)", - "name": "support.constant.property-value.container.css" - }, - { - "include": "#functions" - } - ] - }, - "container-style-features": { - "match": "(?x)\n # Custom Property Name\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?<:=]|\\)|/\\*) # Terminates cleanly" - }, - "media-feature-keywords": { - "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)", - "name": "support.constant.property-value.css" - }, - "media-query": { - "begin": "\\G", - "end": "(?=\\s*[{;])", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#media-types" - }, - { - "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)", - "name": "keyword.operator.logical.$1.media.css" - }, - { - "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)", - "name": "keyword.operator.logical.and.media.css" - }, - { - "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))", - "name": "invalid.illegal.comma.css" - }, - { - "include": "#commas" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.parameters.begin.bracket.round.css" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.css" - } - }, - "patterns": [ - { - "include": "#media-features" - }, - { - "include": "#media-feature-keywords" - }, - { - "match": ":", - "name": "punctuation.separator.key-value.css" - }, - { - "match": ">=|<=|=|<|>", - "name": "keyword.operator.comparison.css" - }, - { - "captures": { - "1": { - "name": "constant.numeric.css" - }, - "2": { - "name": "keyword.operator.arithmetic.css" - }, - "3": { - "name": "constant.numeric.css" - } - }, - "match": "(\\d+)\\s*(/)\\s*(\\d+)", - "name": "meta.ratio.css" - }, - { - "include": "#numeric-values" - }, - { - "include": "#comment-block" - }, - { - "include": "#functions" - } - ] - } - ] - }, - "media-query-list": { - "begin": "(?=\\s*[^{;])", - "end": "(?=\\s*[{;])", - "patterns": [ - { - "include": "#media-query" - } - ] - }, - "media-types": { - "captures": { - "1": { - "name": "support.constant.media.css" - }, - "2": { - "name": "invalid.deprecated.constant.media.css" - } - }, - "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)" - }, - "nesting-at-rules": { - "patterns": [ - { - "include": "#commas" - }, - { - "include": "#nesting-rules" - }, - { - "include": "#rule-list-innards" - } - ] - }, - "nesting-rules": { - "patterns": [ - { - "match": "(?xi) (?\\s,.\\#|&){:\\[]|/\\*|$)", - "name": "entity.name.tag.css" - }, - { - "include": "#property-names" - }, - { - "include": "#selector-innards" - } - ] - }, - "nesting-selector": { - "match": "&", - "name": "entity.name.tag.nesting.selector.css" - }, - "numeric-values": { - "patterns": [ - { - "captures": { - "1": { - "name": "punctuation.definition.constant.css" - } - }, - "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b", - "name": "constant.other.color.rgb-value.hex.css" - }, - { - "captures": { - "1": { - "name": "keyword.other.unit.percentage.css" - }, - "2": { - "name": "keyword.other.unit.${2:/downcase}.css" - } - }, - "match": "(?xi) (?+~|&] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%(*;+~|&] # - Another selector\n | /\\* # - A block comment\n)", - "name": "entity.other.attribute-name.class.css" - }, - { - "captures": { - "1": { - "name": "punctuation.definition.entity.css" - }, - "2": { - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*)", - "name": "entity.other.attribute-name.id.css" - }, - { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.definition.entity.begin.bracket.square.css" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "punctuation.definition.entity.end.bracket.square.css" - } - }, - "name": "meta.attribute-selector.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#string" - }, - { - "captures": { - "1": { - "name": "storage.modifier.ignore-case.css" - } - }, - "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)" - }, - { - "captures": { - "1": { - "name": "string.unquoted.attribute-value.css", - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)" - }, - { - "include": "#escapes" - }, - { - "match": "[~|^$*]?=", - "name": "keyword.operator.pattern.css" - }, - { - "match": "\\|", - "name": "punctuation.separator.css" - }, - { - "captures": { - "1": { - "name": "entity.other.namespace-prefix.css", - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)" - }, - { - "captures": { - "1": { - "name": "entity.other.attribute-name.css", - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)" - } - ] - }, - { - "include": "#pseudo-classes" - }, - { - "include": "#pseudo-elements" - }, - { - "include": "#functional-pseudo-classes" - }, - { - "match": "(?x) (?\\s,.\\#|&){:\\[]|/\\*|$)", - "name": "entity.name.tag.css" - }, - "unicode-range": { - "captures": { - "0": { - "name": "constant.other.unicode-range.css" - }, - "1": { - "name": "punctuation.separator.dash.unicode-range.css" - } - }, - "match": "(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n css\n(?=\\s|:|$)", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#combinator-invalid" + }, + { + "include": "#selector" + }, + { + "include": "#at-rules" + }, + { + "include": "#rule-list" + } + ], + "repository": { + "arithmetic-operators": { + "match": "[*/]|(?<=\\s|^)[-+](?=\\s|$)", + "name": "keyword.operator.arithmetic.css" + }, + "at-rules": { + "patterns": [ + { + "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))", + "end": ";|(?=$)", + "endCaptures": { + "0": { + "name": "punctuation.terminator.rule.css" + } + }, + "name": "meta.at-rule.charset.css", + "patterns": [ + { + "captures": { + "1": { + "name": "invalid.illegal.not-lowercase.charset.css" + }, + "2": { + "name": "invalid.illegal.leading-whitespace.charset.css" + }, + "3": { + "name": "invalid.illegal.no-whitespace.charset.css" + }, + "4": { + "name": "invalid.illegal.whitespace.charset.css" + }, + "5": { + "name": "invalid.illegal.not-double-quoted.charset.css" + }, + "6": { + "name": "invalid.illegal.unclosed-string.charset.css" + }, + "7": { + "name": "invalid.illegal.unexpected-characters.charset.css" + } + }, + "match": "(?x) # Possible errors:\n\\G\n((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive)\n|\n\\G(\\s+) # Preceding whitespace\n|\n(@charset\\S[^;]*) # No whitespace after @charset\n|\n(?<=@charset) # Before quoted charset name\n(\\x20{2,}|\\t+) # More than one space used, or a tab\n|\n(?<=@charset\\x20) # Beginning of charset name\n([^\";]+) # Not double-quoted\n|\n(\"[^\"]+$) # Unclosed quote\n|\n(?<=\") # After charset name\n([^;]+) # Unexpected junk instead of semicolon" + }, + { + "captures": { + "1": { + "name": "keyword.control.at-rule.charset.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "match": "((@)charset)(?=\\s)" + }, + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.css" + } + }, + "end": "\"|$", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.css" + } + }, + "name": "string.quoted.double.css", + "patterns": [ + { + "begin": "(?:\\G|^)(?=(?:[^\"])+$)", + "end": "$", + "name": "invalid.illegal.unclosed.string.css" + } + ] + } + ] + }, + { + "begin": "(?i)((@)import)(?:\\s+|$|(?=['\"]|/\\*))", + "beginCaptures": { + "1": { + "name": "keyword.control.at-rule.import.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": ";", + "endCaptures": { + "0": { + "name": "punctuation.terminator.rule.css" + } + }, + "name": "meta.at-rule.import.css", + "patterns": [ + { + "begin": "\\G\\s*(?=/\\*)", + "end": "(?<=\\*/)\\s*", + "patterns": [ + { + "include": "#comment-block" + } + ] + }, + { + "include": "#string" + }, + { + "include": "#url" + }, + { + "include": "#media-query-list" + } + ] + }, + { + "begin": "(?i)((@)font-face)(?=\\s*|{|/\\*|$)", + "beginCaptures": { + "1": { + "name": "keyword.control.at-rule.font-face.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?!\\G)", + "name": "meta.at-rule.font-face.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#rule-list" + } + ] + }, + { + "begin": "(?i)(@)page(?=[\\s:{]|/\\*|$)", + "captures": { + "0": { + "name": "keyword.control.at-rule.page.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*($|[:{;]))", + "name": "meta.at-rule.page.css", + "patterns": [ + { + "include": "#rule-list" + } + ] + }, + { + "begin": "(?i)(?=@media(\\s|\\(|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)media", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.media.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.media.header.css", + "patterns": [ + { + "include": "#media-query-list" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.media.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.media.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.media.body.css", + "patterns": [ + { + "include": "#rule-list-innards" + }, + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?i)(?=@counter-style([\\s'\"{;]|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)counter-style", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.counter-style.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*{)", + "name": "meta.at-rule.counter-style.header.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "captures": { + "0": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter\n(?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n |\\\\(?:[0-9a-fA-F]{1,6}|.)\n)*", + "name": "variable.parameter.style-name.css" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.property-list.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.property-list.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.counter-style.body.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#rule-list-innards" + } + ] + } + ] + }, + { + "begin": "(?i)(?=@document([\\s'\"{;]|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)document", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.document.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.document.header.css", + "patterns": [ + { + "begin": "(?i)(?>|>|\\+|~", + "name": "keyword.operator.combinator.css" + } + ] + }, + "combinator-invalid": { + "match": "/deep/|>>>", + "name": "invalid.deprecated.combinator.css" + }, + "commas": { + "match": ",", + "name": "punctuation.separator.list.comma.css" + }, + "comment-block": { + "begin": "/\\*", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.css" + } + }, + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.css" + } + }, + "name": "comment.block.css" + }, + "escapes": { + "patterns": [ + { + "match": "\\\\[0-9a-fA-F]{1,6}", + "name": "constant.character.escape.codepoint.css" + }, + { + "begin": "\\\\$\\s*", + "end": "^(?<:=]|\\)|/\\*) # Terminates cleanly" + }, + "media-feature-keywords": { + "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)", + "name": "support.constant.property-value.css" + }, + "media-query": { + "begin": "\\G", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#media-types" + }, + { + "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)", + "name": "keyword.operator.logical.$1.media.css" + }, + { + "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)", + "name": "keyword.operator.logical.and.media.css" + }, + { + "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))", + "name": "invalid.illegal.comma.css" + }, + { + "include": "#commas" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.css" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.css" + } + }, + "patterns": [ + { + "include": "#media-features" + }, + { + "include": "#media-feature-keywords" + }, + { + "match": ":", + "name": "punctuation.separator.key-value.css" + }, + { + "match": ">=|<=|=|<|>", + "name": "keyword.operator.comparison.css" + }, + { + "captures": { + "1": { + "name": "constant.numeric.css" + }, + "2": { + "name": "keyword.operator.arithmetic.css" + }, + "3": { + "name": "constant.numeric.css" + } + }, + "match": "(\\d+)\\s*(/)\\s*(\\d+)", + "name": "meta.ratio.css" + }, + { + "include": "#numeric-values" + }, + { + "include": "#comment-block" + }, + { + "include": "#functions" + } + ] + } + ] + }, + "media-query-list": { + "begin": "(?=\\s*[^{;])", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#media-query" + } + ] + }, + "media-types": { + "captures": { + "1": { + "name": "support.constant.media.css" + }, + "2": { + "name": "invalid.deprecated.constant.media.css" + } + }, + "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)" + }, + "numeric-values": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.constant.css" + } + }, + "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b", + "name": "constant.other.color.rgb-value.hex.css" + }, + { + "captures": { + "1": { + "name": "keyword.other.unit.percentage.css" + }, + "2": { + "name": "keyword.other.unit.${2:/downcase}.css" + } + }, + "match": "(?xi) (?~+] # Selector combinator\n | \\| # Selector namespace separator\n | \\[ # Attribute selector opening bracket\n | [a-zA-Z] # Letter\n | [^\\x00-\\x7F] # Non-ASCII symbols\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n\n # Or one of the following symbols, followed by a word character, hyphen, or escape sequence:\n | (?:\n \\. # Class selector\n | \\# # ID selector\n )\n (?:\n [\\w-] # Word character or hyphen\n | \\\\(?: # Escape sequence\n [0-9a-fA-F]{1,6}\n | .\n )\n )\n\n # Or one of the following symbols, followed a letter or hyphen:\n | (?:\n \\: # Pseudo-class\n | \\:\\: # Pseudo-element\n )\n [a-zA-Z-] # Letter or hyphen\n )\n)\n\n# Match must NOT contain any of the following:\n(?!\n [\\w-]*[\\:]+\\s # One or more colons immediately followed by a whitespace (denotes a property or invalid sequence)\n | [^{]*; # Any characters and a semicolon before an opening bracket (denotes the end of a property)\n | [^{]*} # A closing bracket before an opening bracket (denotes a property)\n | [^\\:]+(\\:\\:):+ # More than two colons (invalid sequence)\n)", + "end": "(?x)\n# Match must end with:\n(?=\n \\s* # Optional whitespace and one of the following:\n (?:\n \\/ # Comment\n | @ # At-rule\n | { # Opening property list brace\n | \\) # Closing function brace (for passing test on `some-edgy-new-function(`)\n | $ # End of line\n )\n)", + "name": "meta.selector.css", + "patterns": [ + { + "include": "#selector-innards" + } + ] + }, + "selector-innards": { + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#commas" + }, + { + "include": "#escapes" + }, + { + "include": "#combinators" + }, + { + "captures": { + "1": { + "name": "entity.other.namespace-prefix.css" + }, + "2": { + "name": "punctuation.separator.css" + } + }, + "match": "(?x)\n(?:^|(?<=[\\s,(};])) # Follows whitespace, comma, semicolon, or bracket\n(?!\n [-\\w*]+\n \\|\n (?!\n [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match\n | [^\\x00-\\x7F]\n )\n)\n(\n (?: [-a-zA-Z_] | [^\\x00-\\x7F] ) # First letter\n (?: [-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n | \\\\(?:[0-9a-fA-F]{1,6}|.)\n )*\n |\n \\* # Universal namespace\n)?\n(\\|) # Namespace separator" + }, + { + "include": "#tag-names" + }, + { + "match": "&", + "name": "entity.name.tag.nesting.css" + }, + { + "match": "\\*", + "name": "entity.name.tag.wildcard.css" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.css" + }, + "2": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x) (?+~|&] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%(*;+~|&] # - Another selector\n | /\\* # - A block comment\n)", + "name": "entity.other.attribute-name.class.css" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.css" + }, + "2": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*)", + "name": "entity.other.attribute-name.id.css" + }, + { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.entity.begin.bracket.square.css" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.entity.end.bracket.square.css" + } + }, + "name": "meta.attribute-selector.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#string" + }, + { + "captures": { + "1": { + "name": "storage.modifier.ignore-case.css" + } + }, + "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)" + }, + { + "captures": { + "1": { + "name": "string.unquoted.attribute-value.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)" + }, + { + "include": "#escapes" + }, + { + "match": "[~|^$*]?=", + "name": "keyword.operator.pattern.css" + }, + { + "match": "\\|", + "name": "punctuation.separator.css" + }, + { + "captures": { + "1": { + "name": "entity.other.namespace-prefix.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)" + }, + { + "captures": { + "1": { + "name": "entity.other.attribute-name.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)" + } + ] + }, + { + "include": "#pseudo-classes" + }, + { + "include": "#pseudo-elements" + }, + { + "include": "#functional-pseudo-classes" + }, + { + "match": "(?x) (?\\s,.\\#|&){:\\[]|/\\*|$)", + "name": "entity.name.tag.css" + }, + "unicode-range": { + "captures": { + "0": { + "name": "constant.other.unicode-range.css" + }, + "1": { + "name": "punctuation.separator.dash.unicode-range.css" + } + }, + "match": "(?