From 26ae101f0f14513066e8ecf5fd5dd112131a8e1d Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Wed, 28 May 2025 11:56:43 +0700 Subject: [PATCH 01/31] Add vscode-css-non-refactored branch files --- .dev-assets/docs/TODO.md | 15 + .dev-assets/docs/selector-rules.md | 98 + .../examples/extension-original-css.cson | 2465 +++++++++++ .../extension-original-css.tmLanguage.json | 2163 ++++++++++ .../examples/official-css-unquoted.cson | 2139 ++++++++++ .dev-assets/examples/official-css.cson | 2139 ++++++++++ .../examples/official-css.tmLanguage.json | 1864 +++++++++ .../VSCode-CSS-Nesting-Extension-Logo.sketch | Bin ...ing-syntax-highlighting-logo-no-border.png | Bin .../css-nesting-syntax-highlighting-logo.png | Bin 0 -> 23395 bytes .dev-assets/scopes/css.cson | 2402 +++++++++++ .dev-assets/scopes/original-pr-css.cson | 2396 +++++++++++ .dev-assets/scopes/property-unclosed.cson | 95 + .dev-assets/scopes/property.cson | 56 + .dev-assets/scopes/selector-custom.cson | 58 + .../scopes/selector-differentiator.cson | 103 + .dev-assets/scopes/selector-unterminated.cson | 108 + .dev-assets/scopes/selector.cson | 21 + .../syntax-issues/archive/at-supports.css | 7 - .../syntax-issues/bugs/at-supports-issue.css | 28 + .../property-value-slash-issue.css} | 2 +- .../bugs/trailing-selector-issue.css | 44 + .../aspect-ratio-issue.css} | 0 .../at-page.css => fixed/at-page-issue.css} | 0 .../calc-in-at-media-issue.css} | 0 .../calc-nesting-issue.css} | 0 .../clamp-arithmetic-operators-issue.css} | 0 .../at-container-issue.css} | 0 .../at-import-issue.css} | 1 + .../at-layer-issue.css} | 0 .../at-media-properties-issue.css} | 10 +- .../light-dark-func-issue.css} | 0 .../{of-not.css => missing/of-not-issue.css} | 0 .dev-assets/syntax-issues/scss-issues.scss | 67 + .dev-assets/{ => util}/get-shared-names.js | 0 .dev-assets/util/get-shared-values.js | 197 + .prettierignore | 3 + .vscodeignore | 7 +- CHANGELOG.md | 6 + demo/at-container-demo.css | 141 + demo/at-counter-style-demo.css | 3 + demo/at-font-face-demo.css | 8 + demo/at-font-feature-values-demo.css | 48 + demo/at-import-demo.css | 22 + demo/at-keyframes-demo.css | 5 + demo/at-layer-demo.css | 34 + demo/at-media-demo.css | 82 + demo/at-namespace-demo.css | 13 + demo/at-page-demo.css | 53 + demo/at-supports-demo.css | 35 + demo/function-nesting-demo.css | 37 + demo/missing-keywords.css | 5 + demo/properties-demo.css | 162 + demo/selectors-demo.css | 2529 +++++++++++ demo/shared-names-demo.css | 120 + demo/shared-values-demo.css | 128 + demo/unterminated-identifiers.css | 230 + package-lock.json | 202 + package.json | 8 +- src/vscode-css/.editorconfig | 15 + src/vscode-css/.gitignore | 1 + src/vscode-css/.nvmrc | 1 + src/vscode-css/CONTRIBUTING.md | 1 + src/vscode-css/LICENSE.txt | 33 + src/vscode-css/README.md | 10 + src/vscode-css/SECURITY.md | 41 + src/vscode-css/grammars/css.cson | 2356 +++++++++++ src/vscode-css/package-lock.json | 320 ++ src/vscode-css/package.json | 13 + src/vscode-css/spec/css-spec.mjs | 3693 +++++++++++++++++ src/vscode-css/testing-util/test.mjs | 69 + 71 files changed, 26897 insertions(+), 15 deletions(-) create mode 100644 .dev-assets/docs/TODO.md create mode 100644 .dev-assets/docs/selector-rules.md create mode 100644 .dev-assets/examples/extension-original-css.cson create mode 100644 .dev-assets/examples/extension-original-css.tmLanguage.json create mode 100644 .dev-assets/examples/official-css-unquoted.cson create mode 100644 .dev-assets/examples/official-css.cson create mode 100644 .dev-assets/examples/official-css.tmLanguage.json rename .dev-assets/{Sketch => logo}/VSCode-CSS-Nesting-Extension-Logo.sketch (100%) rename {images => .dev-assets/logo}/css-nesting-syntax-highlighting-logo-no-border.png (100%) create mode 100644 .dev-assets/logo/css-nesting-syntax-highlighting-logo.png create mode 100644 .dev-assets/scopes/css.cson create mode 100644 .dev-assets/scopes/original-pr-css.cson create mode 100644 .dev-assets/scopes/property-unclosed.cson create mode 100644 .dev-assets/scopes/property.cson create mode 100644 .dev-assets/scopes/selector-custom.cson create mode 100644 .dev-assets/scopes/selector-differentiator.cson create mode 100644 .dev-assets/scopes/selector-unterminated.cson create mode 100644 .dev-assets/scopes/selector.cson delete mode 100644 .dev-assets/syntax-issues/archive/at-supports.css create mode 100644 .dev-assets/syntax-issues/bugs/at-supports-issue.css rename .dev-assets/syntax-issues/{grid-placement-slash.css => bugs/property-value-slash-issue.css} (100%) create mode 100644 .dev-assets/syntax-issues/bugs/trailing-selector-issue.css rename .dev-assets/syntax-issues/{archive/aspect-ratio.css => fixed/aspect-ratio-issue.css} (100%) rename .dev-assets/syntax-issues/{archive/at-page.css => fixed/at-page-issue.css} (100%) rename .dev-assets/syntax-issues/{archive/calc-in-at-media.css => fixed/calc-in-at-media-issue.css} (100%) rename .dev-assets/syntax-issues/{archive/calc-nesting.css => fixed/calc-nesting-issue.css} (100%) rename .dev-assets/syntax-issues/{archive/clamp-arithmetic-operators.css => fixed/clamp-arithmetic-operators-issue.css} (100%) rename .dev-assets/syntax-issues/{archive/at-container.css => missing/at-container-issue.css} (100%) rename .dev-assets/syntax-issues/{at-import.css => missing/at-import-issue.css} (85%) rename .dev-assets/syntax-issues/{at-layer.css => missing/at-layer-issue.css} (100%) rename .dev-assets/syntax-issues/{at-media-propertie.css => missing/at-media-properties-issue.css} (65%) rename .dev-assets/syntax-issues/{archive/light-dark-func.css => missing/light-dark-func-issue.css} (100%) rename .dev-assets/syntax-issues/{of-not.css => missing/of-not-issue.css} (100%) create mode 100644 .dev-assets/syntax-issues/scss-issues.scss rename .dev-assets/{ => util}/get-shared-names.js (100%) create mode 100644 .dev-assets/util/get-shared-values.js create mode 100644 .prettierignore create mode 100644 demo/at-container-demo.css create mode 100644 demo/at-counter-style-demo.css create mode 100644 demo/at-font-face-demo.css create mode 100644 demo/at-font-feature-values-demo.css create mode 100644 demo/at-import-demo.css create mode 100644 demo/at-keyframes-demo.css create mode 100644 demo/at-layer-demo.css create mode 100644 demo/at-media-demo.css create mode 100644 demo/at-namespace-demo.css create mode 100644 demo/at-page-demo.css create mode 100644 demo/at-supports-demo.css create mode 100644 demo/function-nesting-demo.css create mode 100644 demo/missing-keywords.css create mode 100644 demo/properties-demo.css create mode 100644 demo/selectors-demo.css create mode 100644 demo/shared-names-demo.css create mode 100644 demo/shared-values-demo.css create mode 100644 demo/unterminated-identifiers.css create mode 100644 package-lock.json create mode 100644 src/vscode-css/.editorconfig create mode 100644 src/vscode-css/.gitignore create mode 100644 src/vscode-css/.nvmrc create mode 100644 src/vscode-css/CONTRIBUTING.md create mode 100644 src/vscode-css/LICENSE.txt create mode 100644 src/vscode-css/README.md create mode 100644 src/vscode-css/SECURITY.md create mode 100644 src/vscode-css/grammars/css.cson create mode 100644 src/vscode-css/package-lock.json create mode 100644 src/vscode-css/package.json create mode 100644 src/vscode-css/spec/css-spec.mjs create mode 100644 src/vscode-css/testing-util/test.mjs 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": "(?9Dd#T^Q5aSaZ|OM&9<7Tn#6I}~jx7M$V~cXurWcXxtYaQ$-K&-dd! za^%O}%V%cI9yupUMM(x5gB$}64h~!P+gCL>IC#td{%9z$H%9aTVmLT`X4$V28sC>s zvgECkWeA5N)#MKU68D%QCd5aAFu>R!F4SAmUCp@0% z67ox#hnKfweQ{Ce@XcYh)8F^F(y)!o_wLO9tveVohf>gWH{BKoKH{!H_TMZ&~VL;g5R;6{^Eh={%or+Qj7ucNb~U2V7DNBw%TH5L%1wW zVZ-83bb1}!Imy6)7V^u}e!cyHNNn0~<)`S=yZ6HPOaBrX0?&?*Dd>lZ@+bKpzZoBL z`^%sz;Uax79>vjbw6|Gra@vS=aB$dovupYn}<`TGv#@DlcMEmklrz^mJ zX*^5F_c`gc5=R(^Ff2z%M@-!70N3y{Y@e#JKQ_!2Z|*O5HVj)__f$`Mcr_?h~4AQYTz54LukGVF&k|!NQfProV0sA-JTwV zn&~0w0}1Ry<1%T`q^M)<^!4;aJKPU{OAjD_T*SyQ5n@35GKI9$)NU5&cey>#Ws2h( z&c2ANZ>kP!=&2yn=H=%PoLyUc0i5<&T3ZiS8gzO)dtIjX- z{0b|3fZLnzeABQ!(Q5`}`AdcxbwLdq-{8CwZrF8pHB_SL4V=c^RmAq=`tB~nDHRW{ z$zjE^Osj$mVfSRIuA-oze=wdZgZYnQeH`Dk*0RFNA)>!*MnH{V<3iL|iEk4J&GA(J zN0Pfj;wr`4un~=cZvpyko)wy!noF~#>gb+Qbk|XxQ!BVm__c-`Twyral)RY8D7TyN(C1`qRLO}ta#RV zz1hW#mUxB>JN*1#MxCX7J~LB)~ZVgO<1Vd_sPv; z4!@ky{7FPa#C>_Q+QE@;FtMcgHc9tcs_8(WSf+0a*T6M|CiY$D>Mo{`=gGkZh0vYb zUb{Kn_o0SxOcE~MpQ1nZO&<^q^WEHYScLkRnvsWK{GQMfb(_c*3GHn5-S!@8a$Mt* zb`0R(umpqf{ByxgKt%gs#cIXr+ekBK#XULXt;0h{j)$>ycw_!8)#(h+ii!$pb|)Jf zo9iAxVTeODUZb2J0p56zh`<4ogtOy%yEn(Bh#$TzgF%+xG z8v7z-&$zoKn$Z}tX!=_tPO@@Y{>^wp#NDO84kU1~-~?-(|vFBHuL zuu`*V2z`peb8I_F9@7KfgqibICD}yc5!{@RlvqL~8E5@=iTP9!&eK=Y zdF|>AK6qzayc0n53UR{ibzwk5cFlK3UsymDEw$j~<>hl+Z9YJZGj~Vu+Qc=$_@~(X z4w6MvKqNZA*r&k(35agqVD?KJ@`ayCvOky0fPbJaE*-RYWbPw0LiS%Kk__ld$F;xpov#mRY!?-et^`Avhgqt&mMw)6LgS(H) zu(pz`V#${qe}Df**F8B+_r%@0X)4OFm>!Iqoli=*uE^8jq-03LlJteLy2QVEEL^%} zo5{?tW2*!tzTKlFs0Csi6~leP_faiXi(L*|B#D*I>x%f{Fo6cDCtarhN#$ z=sTgz&-F_>3 zyDI&n^<&0Y0lio`=Y2=0JciyTz~G&$E$)VE2q3!ex@ooph#)Or8!ZNg^kp)DW2&XB4tHznLxj^n(# zI%q)343B;_P~tkCB}#JhbLPaL)rFvi1DbLf%EE53Ro@aW*x0%>(_A*TWx`o(#iGl^ zZxS5=EF68Z4<-T9pOjYJaQA4`DJQ$MohBHhlJR#La^{wV6YhhrmSW9p7ia?BYGHGMNi*(58Sr! z?&J}9$27OpmL+dhmF#D-4;JJue#*QN%4k?HdiuOcQIlBrx>nCQBJ}SXh;ht)l{YT6XrU8PTE0=k8)g6vWW`*ov;LGQC3fmt#YBHda=h z3V`cYFY=`}n&U}BuidZ3sE$TW63l2 z!bF*^;_Cy^3Yi>?FZdYYnO@9Nx`-Eaej}dJ6-6_JF&-Ws9S&q0dBaWS=H>$D3emMG zo9g!|6~@~$*~^a&+1c3>_Bmi|Z0vENUwQp_e&E)5l+M%A3bEtU(+<;&oOY)TQAn1w zDn4ANW!8D@PhZ>PzK;pR*a4hp=ML=GtE-v!-0_)S#8R>}_mrJhbmRA*MbAqM8yW9z zZeFb2f^|FnVS0L8C@PN@-VcYm9v|W?@EjWLhLPc*>NXm^HP5MwESrV#hXnyz`v-a2|(4@AuHq9g= zI*$P|>N#P^&FB1y?OsmbA9PDuT@OTb;XN{!nk+8T6v7St8OAlXgY<$O4GnV-6h^A^ zc=zRFw44fGy@VFad5O^XU%Hm$eFJ!rK>uvompXtmPePpd=y87$!cqS zz+)%n0(@+ga=(cNz1jA5iCR+TY(lsk54Rwb4=5 zmG@$f*58B<-egV@^9-uTtfi-?JCeVD|2lWUR3ddoG@y9lzmu#oNm>tU^lrW3kC@@` z5M-2UkxDJ)Vw(w?|8D!{bb5M95YgjjDBwX=?R9RHDgOBMWC61m#+xGXsV5QCU&P&n z87}FO0h1&XZ^}SKZbL#f_k<6S1XNE^Fc6ed@IU)e`4Kn!$dDpUT3tKRcYi z;C5cbto4YZF@EvZpR#0H*QfOp{mw+`x4w&cGu);;5?V}wH1*#9jY)#^+)kGeT5KF> zAW-Cg4Z6^857-5BZf(TZ5Svg3{4~3#8aKO2l-Tn3pXdRFw|H$eJA*g%O$6sRXwgK@ zC6V_pw>mpTw^}W%tb{m-%T!W{Gl?3fqXX2-)KJ;78a_hsQj|!m!Pq< zc#;osSmQno5npdLxs(X0zI(2%fh13GRiUSanhQ$N|5h?% z?mwMGtEhVsG%(f+{UAoyI>pg}8ry(%xfkhb4r_Mhy+ ztilk$)4)g;2^t+!E}3Jru9DHj)4h=33WuH3Y^&?Ig?;b(}JXlZ3-XF~5By>j*7;9zb- zOmy_`IkSD!ghb4qP=p{AgfB8xUWGd`cdUcSksG3HJJi@TDe|G7-A}nBSej!*KMLXi zpH8MWMsv4np(f5wn`4~^4%hX9*`yD8gAZt8g0R)DN-8RN*(-l)wbVOq(9QVmovt*= zD=EbT?66eV8y)ny*xBJ zhucGoh1Z{pXWxi-MiUeI!WK^UL3%Ot4vX~4n@72Q_7m1eQPN5hE=kubevB2=4J=F? z-0giKmd4R+#KJF`ZH;xy&11Hc)&BtRxe{00r3FK>LV|w0SY?&tFqO|-d&`nNC zLJPryncCmL&7CwncYt&3G;uUr0*5BJ%m>Ta(bI4r>4p%pBnOWznm@Viu(zW262b|LM0>P z8~ObkQms&Q}$b9l8^xVOWkyb_*A)UsJpwn zKp;WI7m&JxIb|eG`JV@{T&;w@0w&d1kzL##_~GH93wytE2RyzZP7I*S{|c#ycu!8&3gkoSDuSA8`(UkxeyJ1!tPbEr=JG!5wt!hn~>E#=Px z<#Gal;Ua<}#Kotb(+?{lOCuw;&B(sMU!>#JY^tGzYYPht45F&VEYWg`igTYHv8ZuL#y(vQ(s6;D z#)mana(s;%0`5y~pY}+YZyN8B-mrI^G-AB2ONub5S&jtDYCqEtm3}{Dyh_rh7Ajp9 zdy@OpP&^*?p60UPdQ~{vI3RJ;&;0-{0Z0)m-!Cw_@lE+RsN?YP@T}U-RHZo|Od7A4 zCR3g<46GD!k#l%_Ua)c( z7CqYdT&5@X7<-5}*Xp&7i42NqYh(d>`OC>2uwCKF$s}i%mSkdm+OTnQdh>-djvMlT z|L%`pp*HR+eMU`Um?r!=}~>rXWDJftA)x$oa2BOo|WprexB*d*rX@|&At@~;RQsXnS2DN zvq1V~foLFAd~{$mdQaz!C0j6eapJL#MC0hx>tN@?6-ExX@W1c6$kD+3u@qH{l?7J7 zatL)(Bg;`bADv+;EOJl^i+eyv{eUJ!u30VFnW!9P`gK;-V3(U~<|9r%-qZ9OUR%PO z0mH)4ecr~ugHY<0yKuX$QtpM(j&m@7$6bvsPNmq;QP;k9=eD3TYlkYUS>R*Z=Hi@; zGS_MIkN6)G+;!F8wrX2F9oaJ%>znuJZP#C~9e z2Y>@_+r%CV|2Y8WnD~9c6@%~MOxS^|GOqrZV)rc`7gTCjKoV;GkQs4fJDwl+Tyvk# z9akTo)Yht1gSA0biTFwj-q3%pUPpzNobBpAb3Q-?12da$uG*IyPU@(`&O57qy2#J_ zZ$q<;S~DF4M1v-ung!j`O&4-@jAG zV4cPgi}nivVh-i~o6+B&^QJqLRmVGa!iRZ02s2rRbe6haGrbooFVmoim5}BPdeuOq z&)HTpzm&rC3^G+J-+>4jOGLpjc8o#++vlF1o&gjV6% zB4@-~uGk9}PuXz?b{0$4-Ro}*z;*k3)Aysp*V>2-H7q+tZAA!+-?qQ+x4vT)yVh|K zMflOJ9-8J|1ke6tk}lRf6f$%Cqvr&E^Mz$34KmsjUQjHgp{%St$)2_^vKb@pdiEo= z@}U=Q@FkaBpVwL&DUt-NRkE6sbd62fP zl(Ya)$Q9g=3B6Trio}2?qx6VSa9^r6dVL5hO0$@|-e$r!yk& zDW*gf8kBbWa+dLQviZezVGmPEj05`KZVEd_G_S{n5vWApLr+Q|5@@GNmJUZ~3 zGA%zp|1@$>I35!w-`Tqn58~J`SBv4E*o{dan9CX$j`MLBX z`lu|D|mofOZRp>iQ1D+c&ngJ1pmg0LvgYBB0diQDizKO%4O#a^BJc`<PET3}2&VlG(!I@u2zNia ztD0v&jdr3S??*>t%+1agPy*>bf=md@@%zY72KBF-&jnu#0%etH7#N1YMwsq3To2d9 zIBv9J+`xh=?ipF^{c!z9wxaZ%Dzkx$sBz)A2#KC@U#Sa*Hf|2;7%n~Fex1$&OMW$) z3O6+p*zfbqL3njUaC|oZ(`CA-9(iLeF$aA7zU!lS9i_lwtu^oKR2w4Nj8p1iZ{xf8 za7-q(E~DK;S;3)581wRN1v+FQc9N&{+^3Hnfb4|nf1}9xV!{?a3t7_k!R8a zdN%Eex?rKa91L3T3=~kX=<<0P53i^!D}^ghnyy(4lZ+g@wByS~9q=ufwg$ zg9)X``y;H0Zti{EyuL>8Unc!ez;cOxSEMZp3PIy}wdIuSuOlRgOT z-{z@v&cribT1DXQcRhN*g3Z9e-^i=vVDS~WH0^Z>Or70k^!l6|2H)GXQTRV7USUwU z;ZS7m`Le@Q$odrNXh*bpL+vWm^v`9Ss~5G-TYyJ}-vh`fb%%U3NNCT}kTC{pze1?; z0YotX^z@x>c0lnPumMnTNc>zyG188WcayoHzsMy3!R`TTY(~Ind<(}dn{tk*$n-x% z3gZXB3VgXqa&zp#(97JLjg7PS(ue;9Oo+HDO!KSVsqhCD~w-N;(d3>F+u5BYzbH z4%%T_mRU!gx%i#!uuu6{N9tZTI?L#6bD_P6p!-@nfCQ#FIWZ74QV8mSjkUFMUwZ(Y znI=0wf|q2K?hEVlmX?-?!CG2ce=d0Y2JwPxcO+ZGYc6`n7w&CXK^5iY_)7~|qhGD#I2X%sJy_cz>n9q=PaOJ^8gCIv>CjsrB%2f=n#S&DaAsqDbj%uk9QiQ*ufI$Hhb`K*g&H z9O3rkU5k6qpRDmO8QgJcX`GNCGP5x%Nda4or6drO=wF~|N>58Y?~ln-noRdrRwGCr zcT~F3haGG~NWhe+7s_>S7xAT0A@-8_==XDmUB)K5bxKxa>OK$8tA$5#44sq2US><4 z*iVfkB1hjR=)x@~8Y#*i30J6(M2qPQ*S1sTIj(~!C3*z#KqmAju@?Co<3Yg}QdbiX z#dW5MOGpB3|8y>8Rx$%Q!4ER7D)Fj~Z&I7G{hVLKeN6ARjXig0)Gedo9tXMVd z5&c+*dH*u3C^7EGSaMLPH1QT@EE*|8^}EVvI8u!cwm^)**Ctl1JEqT?D(F{!vTeiY zuCF#;5iFvg{-fUh^Nh>)A+#$Nd>oz7B^hxN070M*sBip4XoOp;eFYDdJEGUFBbDev zq0QwtD!BW$2t83`h3fo))D9&5&Ct_)0rhAzCFi)cW8F)NGmh?cB>s+QhJpG`!@)`5 z;6M5}|3{y*P&9Bgw^m$08a%~I|9|u`<5VAa^_z4&V#HTda0=mLNm9o)?B=Re(@GWbnBO}*A*tvWX?p!1lDoW-zgS=Uqe8ZK772N@=l z*0VjUQ&X1}fc@I*0$Rbh`y2DYY+>(OeM{JVVJDc0e8eD_%2T#c$xm3Xp!qV87n050SpHB*QA?R z*GB?3IQ&??=|>j|zO4?50#EpGTVJvr#=Ag!&t^FtJaDEGbrtKW2_0z)l;%_2fA{Yd zKKWDTj>_-9674bP=i+XerR-M6*zOn`-TJjQZxO>)DtsD3YuHEv9^7(N~N(@jAgU(IW0nwn7^72T+iz+r7^|7BPzeATYV60S$MePx0qBgPNStBhD z!%N;p6x$Vi1EKa??od}E!1x#;_%?z=Nv?ugzM`wio9*^vlbRkiYr}PfR`4Vb-I9Dg zC{KnzUY@smwRE(2T@>)A?=}MR$uaP;w5kv&9ZSj6H1xR`d}C+_*tuHV@Lq<4a&{T` z{E0LbM%$PDhr3BhvyztUFE|4NcqatGWGRE5XpQaXwb-WS78Y)YOQK^_lzE1_y54%O0MRN~ zuGa29=bMPHN0gKifm?x!3zW5$^Oi(eURfEBp$E{{)eTn#s32K_KF_X`>Z(Eg_Q7fY z_h}%)O1i>td`Awpu(063*kPYZ9{bNPXD$y3-WC|E_Lu~Eey(|waQ&5@PR?xE^Gtm^ z&fEO=D0;`wTBu>(R@INlZVmyR79Iip!vmNgH*k+r;RtD2xHU7@TITd1Wk)1@(F(@>ec5!MEo%;+BPk#Enw*%A|ca=Bev z-F-eYJ?&UfE7b@fX01oyw!dkm*g=rD;$!$l2xd+mxeEzz%%B6>=kyq;si|2Va%{P0 zhb#?kCt13<)KsU|RaZ~ms~ z!f);Fn$JY$B*FU#gtQ3wNBb?Ratx*0?IyQo?2Sdt%|C;V5ojFiX`0d+{3) zxMNR``C2se-TX$9n1~Z^Kv0fm@CbuNIiwm1I-@ySXxB%s{+@t0B*%m;B;0ASy629g zKrS4Z$0lY{!3)aToVe4mWUg^#`9ASr)f?b?I{rY?IJNg)SM}q0LuKN(pE)Z8{2a;t z7BRcampkoGJb7FO7OVK5zO^y`Bm#`;sOBS|P794M3U3T1eD;MUD7`0&MDiG?=y0M)iaxhhQ|GCxJD^LNE{zlc*8u<^Eo^xoaVMRGBdEF zl?zuKr)*YjM`jS1MP9wHbk_JtV47#Kc9X_6d$l1cMvPtKLk0=G{p=0YowCuLl9|`u zf}YI;E-Z%kL_ER*oKWPWr&QI#BfD5j9Xn(ThC#c`ia_&B#Z3;3VLvsHy0}OPLUWk>5eud5BLW3l;chfwit_H~uTc`xkUQ{?dbY4u4>& z3{MlJ@z))ntC!IdHDn|T2SE%sW%T{p+S(OXls>uIoRYiDK$P+JdpMl$GsN+NZ|e@a zEM3fOxRr^NUcF)~N4qWQuDW7Y^uG$$^#uPB?Dgyogfw7jT#8I&@$|3<@A4k@is&qz z;HM?vPLLoQlQ&LywIw6`!Sa9O`m4d-^Nj`2x2hiEeRDK75hjg1goK-WKQ#`(40Nu$4=m;cxrG@KezGb!^G%j{itcdX~> zGF@C;TxTGjCNsy>KJbjGXLV`mA!-cR($X?daGedEC`idu0ITmDP@ZD7kmQR3g&|rH^cns~2uH`IeYxFzw=aJ+(4?19LP9$jV!EEI2WUj7 z=qG8UMM>D15W-OKspIeZ+j2wUI885fkwrr$WXzdt{(^}YnC1;{~X zL~M3W?OoakN#g_3Z}q3p0EAXD-z~4U!%SV|x{wEj;a;_O6r)RbUGmhJmM}V_pa!@6 z`Ll(XkwL<$ll#9p;2Wx9{NCEGYk);Wp#N6Mx)#sTUj>WKsXUqqWRFeRjX0{xps8%X zO)K~KrQ7=6l(=_#E2vC0W?@(&*D>>iq-g;odKko)WLrdjpXzKwB?V%J9vqG31S8*8=JuIcO>Q^1KxzX0{zFsB~eHF zW4fwV>|1C>EjmSbe3cBxf1Pl!hBA60v2z>a^qcahJ0=*$h zo_@T_K+sWrQJ)yONXS?4?K#aTgvND^IGne0P|38?7XK+#2F8=((Iw-EFgH>9Yah&9 zr7l=pwLjF84Jax6zFlZDH+n(Q4ZNtH{Q0`X+raS5VGY9?4fiiL&UL6u%dY0SSzIh_ z5O-7hzTSmhfk!!zJQ;LmIT0r~{oj9!<8=_!HvGuVPC*OtgHrq!y+@ zVW7$mM{+^zw87L;k#!#nU+AJfx&Am!p;qq^S>JWrs4}jjVUNA9c_;R9Rs9KKwaXy( z77n+n-}cI`T4UBP&d;lO&_@am4(+yL zF8l@jV^_2AKV8FDzKvut*NZ`72i(*Hojb8a?(Db7JANfw@53LE5qP0aAWV{E->^N< zI{l>pQ_`ZH=-!Bw`_V2gL3YdSlZ9rLIk|#xe$jEGTOe9$J5%;Tkjrhw9sI^w5h7G= zneIrD9*e=IMQuM7h2~iR{eO8qBn@(;cIr4Vx+y)MM?cWB8!=Y-Zs+oXN}YUOiPXo9 z>Xb-7z0Jo-00%#c{cDB)Ac41F+{*IlDLN*+aU%XHCzxq(dUzBIL^0_8Q|hJYGU0lU zyhNS4V92T;^HkZ0dwe+=ez@voi$*k+@S>E>1lvb*j2o3{i#kjXs+q?=PYg zAaAC;?RRdr`kvz;aBI1WJ%Hdj91*>!&ale$X9DcLAT8)r>&&4{-%)#cw{u$MfnDH~ zg>K+Qev1xbfT`I)tk+Ia6yzEUJf1%uL7MabbWLjD-F$u9H0OWY%oHst4~h!$r2qiZ zXUO8LrZh5SH5TJKCIOxna5Kw@kv^zU{UP(dt<*Os-&Or#4Dh9T5k@!Lif>bCU$}(f zfIkjuu%MN{dRH*n!k76yl9uBj1}{<$$g?$r@6O z4ceHH#d~x*33%5VEZzTm$dz zeKDMB%s~lG8|X6HA6a*cFZfs~5=;g}Xu;VpR`DEVJ%QI5>CWi%mSk^0fKHvzR>slA zIg*h~eFDjUWKZ0J`14!8dt>{SFNPUc^p5|Bufa~6af2KTjj>3)=c{7_$OrbACMu0= zqtvR0iH3fwWfOSW$h*=6G+Oj-gZt`fs=&dXEL&$Oe{=rIm_NvXaT@Mr zPqbVr&!Mx+x=D3{Vr?ji6XVxKL=$M{)~3M0oPakZrp@Yl#@*u=|?h9U;pot~N18);W!2RjJdLR|N(Bcr4KI4soYp1mq80)4yi$jHb{ zd|5C=;|St705taA%4=QBtmTOitkc^}7LLY$Gf|@z@a-8+F=Ir$pNAi5Hwu~SIKyc83-nSw;famV34S{^6 z5yb2U2fjKF3V>M*Fn}yZaAW#zYU8b1TuD4G*~LKe(?|1dj|KVdYci29BwfyOw+3}x zOEHTfsY$#sR{n)Q&39%*wsqQbe6akGhBro(5U!I%jwMmoW1-$XQ9I3D1)k^YW6Qzk z5_|j}?(U0v;D`B0`J>}wGD;iVPuQjfHC)1(&!ZC)BxyC=dS3y26z&S;FdSJsXmv7G(7!r#5r>DFBY z-5!V`0%KSzvK5vVYOEtkR&-+}qH1VsciGPfGBC*Wl>cXB{QvJOewtZXSTMn+g84of zz>=4m6Bu&nr~T*R;^O}n_`kQXPyba=V`Jk_w$B6;U;b}`H?wp5!2IS)0pXmoefh3y z4(r191Rafp8M!#MYDrEYAr@P2GX|kh1YBY6Fk|B%QgYjBIP_RmWqB7vO9dwL?9%J z01X&zP8#a#pJg}&lHy3}Ak*Z6-n_%^O7?^+Av2g zZ#*M++EV#z9158z{QH;rh9u@6VIr^uCL~KxOs_X>&PDH&fs+Rra!(b*cu&N-s=!&c zWR3;@?f9Xt+rPm}=5mey*Sn8PpA)3Zg7dx!LcN8(NeM2&26x84ok^12tLL{1M~BKU z8na{V$H%|(zSs;F_PtdY&lF75q>Of+ZgGR`9?leV@$k@k!Nf?8t~21J)$dkUOVOHd z5c<8~ecQqr42FMoF!gEcZb};la+FgqT=Sa33qaj@FY3oQQ>?7~5^m4T*rNhtV(r5G z{H@pk5K@5G1j$h+1(&iVy-jh*hp7x22MBrfyY;=gng9@22^f@ym+Gv-&E7h2V_h7y zD-9CH$CcnG;W8JZU(JT%r*e>?F(e#Z5%`h1X6(kDtiqmwfnr0=b+43PrKPg}&*G4Y5+13mwGac=>_8tt@wnd6DgWTVE>VYHzGG!b0@^RzRDU|YuO3g| zzw$hA&fQ7hrMdw{eXdLFe4p<*;XHSz2_DVR51sl42yX|ghg&t{g#-3EGk%-3EX9u^T9)}M$^mv2v&KGk17 z8g}?3>oy%{hy`{|dciJr0{iow0tgI!j*(}z<#SgF?_cd?xD+2b!U_{`%^S5?)-O`U*@zOS$EsR2tx%y}J7)4?yY zB1{iezU%#@sHkX$04>+83%N5n;&4>oisbswt8CGKo(UC%bN%LrP>2n-Pk36HCKo@M zHEc@KsrWo}UEK5$vO*(OTE#~0f&(k*Ex)&nY z!munK{~)r>_Xk!Mk;Y@AR)8~ebG(2`4ft;y(-M1=$FY`X$Uj(F1U6W!pONGvZQp`W zZua9_IFl2yq$W(x%w+%Z0VqIbB$(aYmf&hbiXx3dbGsN))w14hdmvw$!W5F^oK)|AQ=6+G?2{I3@h1Vo7D0tDuNQscbzea~VALl;jSbMeI)c_J2C2 z>HFn2-`HkrYpcQcS8T7reNnTZX75)i=(F2Lm(b5du-HNP?=!hS1^@3DSj`a10C3yW z#rHLiwp(F}0{R?t@E~)Kkki4SoQ($DXZWuuV%q1mWpbv||LNku4LLfVTI}qR!=O1x zDp`n~gCn)4N8)nBO@bmk!65vzeJLX^`IPclx4m5L|F5%Iy!>;?+Ff=ASG`}uV~!Ko zt^aqNd+IuEwRhB_tZ??Knby`Il#F%G7jKMw^ zZU>gpGRT;Q{)7y0ntiFWrUPYB&5a~ane9G15Ke7$`0|sfmv-GNMiF7n1(Bsbc)@TK zDx4sG(YiU>@2{7%Il|C@*KBi7Mp9?@rYd}JX_m52c&XXs9h}y~x{YN=sf|-Yf&? z>-Fq|wkat+f{WU@Qk6I;@n82cT<)Mgyci@qFd#$^j z#uU?Aq@>VEahhJT{fFW^*sa$+e}LHM>oE53g$*5^osB=gToWAAOOKuXWCfUUdK^rC zAU<;*-#2&+wwpb54dYY8fmh!~61q8D>=`rm6RZ*}ZEWOo1aNaBg8PUx+PEjgvLG8j zbOTKQ$0Bd{KJzOp6tONstXy9{vc7BW%+7uvfUpaE!u@&t2+uDh{?W0eUZ9StG$ac~ ztL>ms^S2kxG;`AiE-;jXcKSdU%lstfu~;FEOKlZ2WU)f&>YzHex=Oj&1}|uXJThpdU@+jBJEM~ca?R9b{(bIC@t5RMFuZ*+jFH2 z(l3@GaI<;>Z>>nha#cSNuVaBxEEE3~5Q1l~h=_gDnqakgPJ|4#xn@8%Fv#LhL_ty>7mB-)xnb(4=L6sg9Z4iT zF_pvE5jF>eASoF5+rpXMzDEIO3|u&~tEaYTx^ShLqgW!kb$;ViSLz1~-c+f>V_L!t zrO-&XG*fA!=)}@E@sUc{rZaI6{2p>=yOfZbbLwGso05{eef{0S)J~Y59H7&I zQSRhMy7ggh__ee=4KEcIPvQu^xNs3)?astP1~#fh1V6l;WTCrj>=6w?uIoikYhpYW z)Ed}T;iU*Zq2r;;qapqHO1y+m9D@`E#Sj04U~5vvXfGc$x~}!O!|11 zhubtA9zEBplDcvyByiV$skl3CPX8h8JPRrY?X*~bTBn8jdY#>M_-1X#Ccyh1Be{8d zKcMtUe8btW7JNIv1l85mQGt$c(u)5b=xdMmYTH>HQUsm!0&k%QnR$t1UCTbrY|O8Gl=SiU>(?ezl$HeWFb4?C|kOR2F@RbUsUFNtL=b zZaVY)v(f3_mTzb`B3VI(rj_1}1v_aSi9>m>Gj3jr$v-~xaj}rW!~hH$M9b&#QhY|S ztsU`&^&3?95VstE*=Pt`k}8XKP1Whnxxct|&TVnA1^CDqGYF-!Vul+G97I)I@#yNB z2z{mw-x*tf{qi;EH*!q4&!fvLi_>Gc!dko3RixclVS1_SH}>~{^YxeVP2=}`_PEsr zN;j87fh4OG+?EQ{tsTC`e{%S3>OyK$?LB`YkTt&}a}~7U$UHo@kt>0GFD(2d z+_g2PrZKB-jkwtU7Q=CC_qJ+)FKN&lP;aN`UBc^fxwdz14HK{m0&oEjrxM&Y#9=ry z@W^=nYk6s+9`n|YwYL!ke1w~4JE@ISap8jr#ngJ>mByAxU+Stgg4_D!)|a;$yYqrr z!tqlKAEa)-m(Yw(_A2TY%&21n4(-9Ky*YTmCmIZ%mrm=)jweSp3l8lS(?9JFD`v|K zEG&t4Uy-2^*j^~(my2e1Gy4|r1Ao+}CitORaAav=?^!RTUjgm5LlX9*#1_kFD$Nis zBu9JXir(ndl+18N}yRcSe zu{9DnXe9L+cIFOrVuMJ0TrubhoR(Nqet7H$9#aQQ!%~41pZldJpQ$236T|S4 z);$F#_&)ll(3wt}K#c*+9+ssxpk5aBv}eLauGHiAf4sTwBf^K@zrN->k(uiauM4|t ze|ue-&Kr<5I$4`?ysCP@kcLUA8_xf-EQN!(x!B3j^D>0pYnQ}8mm`CTVjL_h;g<#L z(`FuPt8Jy@YQNI9t9^aM7m`yS^?=U9s2{EG^M@vL&pZQvmQkI^bKneYvLTwP>)%v;c zq)n!l;JYE)N+yQ_N>S6#v%2Eh?puJYN+tqjVwQ|_W)^s&i?947Q zT+6X{QKv;=^Vv)%+gzdL^nYEEX5kFfwO5@Z!so@E0eplL`y#>h6V|_l0I;7R8S5Us znc$GyOZ6m@<)STv3qTYq5|LOz7P-LwC+4eN-Po#^iL?K z9m5A3tel)q@b3zG$7WN;AVqAS%U~t;k%No<-`cpi^Ic2~eIMw06H|WPH8U;&BZBD} z`({=Ao6rg9g>Bp|%?|0=A;y#G?_{#wfSy1l2|(Eo{%P9o)$JeW!`Ih?$k1$#o@Z+b z*bTyZ0x!t{|Gxmv88PN+eYuP~qp#IG@7uR;(sKS~Wo1!MI8X=X1cABY_xBV1jQ~Xi zG$JaT_8Br~zNUsk0JihsVq@OG=wQBT)$K%=^MEo=-%lt|BGV!E=RN-_fsG>brv2qP zx%&1yhy_hhB*sHuZ6C301))bZUm!vH{jsNU+vM~r)H;qs&?U8W9(~etOFY6W$7T$H z!2JWOX5v1eop7o20n9OnTb=aBN4W+aE0OmMITFoaCUDe(mtLox^YA$Z;+*g|;u?tT z$KNAbHk-=IN^%eK8Z(&letfiK9{WQ9h{t{R+rmD2AU{K)_m4pxWzgv-%J;wZO`*>f zX)R=bac80JJ@1wN-CG$DVbw2;#@=eFuOqc@r^^ljIO(TJ+NROe{cl6DmQEtfHY$1a^MCq z_&IIJ8yE!hxI5O-mt$!=Kg+3`wQZ*Wc)(7%Z1r0Ic)58ftQZ%7zy6_3n#`5=%zu6t z`2rcIKR`e5q8t&`ZpN1F^bePbqNi};7yuKf`Fy*y12N)J>p6^I4xERZAV5Bt3vr_% zN1_SL1e{VJ^LYZ_$b<)aBcfh!$Rk&*44)?uOljV{c`<`|WA_W?zOLy4R0o`PqOgzN z9@s&Fm~;G*Li?#I^}qwZQqbiLb>>ilccQW$-;i=**^i^KwQDz>7=7% zb%%3>^Ud%qbNbE8OcUBaDCr`c^VasQsOLO7j*o_Q5&HalNGApI%8Ogc5bu1HZ_GZ& z4`z6zjH|=V3j0h@hNTANn%xJf2b~*pz9*2S%0B9W{KdCz_ zI_R+=HC2=?Z=ix1%bdQ%YmQu6^zde&tK|VxXVeYm=vhvHx_{i<0b;Aqkx&4 zF6F42IB_C@cz8{?E91G#as&V_$vT@d3d0$A17`IL?_6+hZuZLTe_Y3syxbFy zlKd9Om|Yn+<+K#qwq2U7by>$938(`WGoJ;D7K0vkS#zbr(nN6?cgCegGo56TiOl(j zHH}{xAF~5B|hL1kUx;pDhOr* z^|K!>dkSFn*J1JEYKR5QYU%r~jn9cP?ggy*z?i~dCX+b*qe#a+>XsUzoL4-QvdWkv z(E?`u<#P^3j&Jn{b!W9~831$ZjEqL{F|W@{KbV-dXIrhqlBMnmuMRMWf%$5`_QL)y z?{%wJW*zH5uievhH-Ld3Iw1AoPUVPqI;eMACeTlp62Q%YutjRz8 z#J~^#uGVcT$fS-}z^v^6nSFf5fAPxf|7&nZ!F|C#AO+^HdHt}K(~tgv8O&o_og%nX zK0oMFMvPaja^p3_JQI{*XeiO!`0?X68Se_fBT+g3u$cYrSk!=-eZb=;|Kgwg<7aaq z)fCoN``(wakH1yh`3bIIHslTTyE6Pgvj@+f?*|DJYFh3;d1~EYelRMSv2=k^I9(Ov z=NMSUl=WKhXH%vhzXDF6x=2 z^Km59s5x!xyj16E`fAbdy-;dP`WCLJ+ zYd{C#J@<6EATW+aZO;1EP z&xuJb6_i_*kvK48v4$u2fSV>?#p0q3OB9O_tC%mhg4vcg&<|#Ss1|fi&qC_3!gVr? z6#_syMldrr-owv$9=PCG0skpu=iH1h>@T*0*$>UCr?znnR!hVTX8hM&A1)E#OGX#m zLZfR|({9d@=$|M=eI{HlSmxC6H(*?R<~>Ts=*^VR&H&~_P5!A^Re{y~P`3*Om&E%e zorQYb1VB6OheybU{0zCf%lY)f4FPCPU|ySh9{ao>pEaPS902qGymL{{SpM9rE#u() zv~f&r!%ecd?b&LD)9&*jE87YR3aC@TIx81pXeb$*_ec7VF|!GG83*RW70l%od|~;C z&MN`{z{3C2IwD_R#Xa{c@CsQk`*s6&hg-!`8gZDw`o(>I*dR#p0o+h{Z7 zNDNrm>ci)=^DQ0pAD^2Clam3L$05KBkn0O(Chl}88Um)iWop1#+wc{?gyqU=i6~&+ z?Lm|>`+)nyptwND>&VxK6~@F-?0Ez2!a@hqtR@Ip`V9px>u{=zblxpU__FuNc|bxBuY zuy&fCrnwR7Qtf{G|S+W&E5^dAPZ-pNDt&<7;YaOp|~sSFU7Pi376(vy)R(7c7HmCMd&5SQ%=_%?oa_ zJ%;!h4$KbBPEJi;0|yQ?Tgxyq)X>}T;ltYzQOOwUj>oGVm=gqM@9}t)1$*Cd4cMXQfcWWo2cueED+Wa~(Q# zXv%+KoSYg$FwDw3*cfW8t+ceXm_ZmHNsJ@spT=tri2Bq7v);W~^v*v(%70;;oEktd z%nWAO7;4PT8|;4LU^M`UJ?Ec-87tbkG9txu`b4}+`7expnLZ0X~_8( z05k1K1LUV>&c9%2##O6UrTiDi$tj6~S!O81LYNpj9JX=e#;vvve9@vs0N9%Ik9Nwy zn@rjCMZh`5QcDF@SXd~`{*t|yRT>!?87coI`agT;&l1NL#^JXJmrJS?_X`AWaY+%- zPPm;Sii8y`EJW~cp`fS%aT*0hY=RLs{sW7x0=LTtNs)Yllp!RLCZsWmnFp?eurWFB zW@cw*&+ml?lOk(oX3w*;XU_S(LgWap!dadTB`vE+k)Lt5*=$~h{^!TX$HRL5-QJFh ze-pi!2L}hQj*pM8=e78~(GMgcas*ceOBn~^LaR`*P5Z)eyU%jBke+|Hh3PY`5&cv~ zDr4@L`F+t3AR%%F|KKXm-SUN1tjKEqBVBwQJl8xiF%i)7?}rZx5jT?4!@r9Q9;ik? zfP^Rj92G2O+=m0LinX$`@+|Z~PEJm^Bm8A+ss8@{D_}VtQv*z4Am+ZlzUalA?#0(~ zIU(`~H{qyYG2=d~YNgW)cWBY=PM*76%sli%#5Z9sHTvapJS_dc^KSHmNQg2M-36 zN|Hn&MjbpUZZsN9@kBp}gvc436f9+2=Uum4W`@R2;DG_lz9eQLMCsrnkGpW4)v5OO z_TC2`9NQh4qL7${5VhHQjpM9tH9tQ;)@rqa4{~yHQeqZDlm!k7_7!f!aaPAllH?T! z5!ix?oaLfUx}++ExDDJB+%gJqo7J_Zr>F0y2m8%&uoANnqS6oia2js2I@j9T+JBzO z^s%uqiCGBIHE@o{Q8>-&UTjlj0k&gDAiMnXKKbg`CxmDN*Wet_W)hrcJy4|2ZF)YD zHa0eXv_+C_*zEe_r?=;y`t=DR+DIddU3bg4%zCJ3-1;W);OS+@)@x225Piwtfh0P9 zW^tH-s9&ED;wE^6aTqLR9A>>%Gcz;)Flp}i%D`PRcgENG%LpyoqMgNIhM|6aLI?od zf@53{{BW4{V3DJ7#z8vrO!Iy&l-GS0GIuerIof5{Pce1%{+OBVM@%O(CykbVtX zOQ~O<5Ea`>pf(vn+;MUtfRW`NTRnIJo(+FKal30H;1HLTaTKnyh>DP-N1iDSf~yAx2C@#) z&O2uH>k}ega0d=?O<~|Di?00JwOQW6-Ey{k&T(PXuTO}0m&VccCxUI)`Q|pi^PI5V z-Cb5^c$d$qU!M?>vbDA4k}^)R*lTun_CdN={j2B1rO8g-i=%#hLc|BIa7h^#;UrV} zTT$`QY-ni6bAi;aPl&j{5xBzNDoSNprE7U#J5C^L5eYL=?57Z-4cy>auM9ZIRBc%r z_lVSUu5TtYLt+*}0N@1N;F2=#!9k{KuZ4w$2CEgEPfsOgAp`(Ua5)6$;2u-`H0bCl zC(8k$^U=Q&aZI;#$LKOX=5TZ;NjMdH zdG`loF*>iUt|LNp7sfJU+AU?=fkRAH$j-orr>Ccv9n2}^`sScNA9KvyHSg@~T#by3 zL_c7J=mLyox0IXB<|XbhRiXU?8)*a6Z{CZuw6v7hn}c3{bh+kriSg9t79omhtwbftRTM!GjfjPXh|xmH0g)@Bh!`b^f;I`S z3*;Y2pEM~_rAZqSNEZ^4yvclxNO&*Y%*?sx+%s#zSH8x**1U7i*|T@@VG*LNPs*GP zoDoJUh(?;I2(*qoOX70zM>Ew-5Or4bo?6za9>|v zCm$Rka)O+BbzcByf{}{X?(Xi_6eQ<9XXd;$6= zMaI;pY!bgA==(o&qtURnS}pl72@wObM5a7{$?8U56=!RB3_b#G`q9Ije5iy7fDDl( zuME2EYW78yr_wP7(AOBt%gf1!O9&sxj_-pJnbnlIife6cZJNQ^6x{Pmu@Coq-$cA@+GLTK}=tGHa-)5aTMgZR-NCN`{$p=vgAPZz7BvWKk+pHzyhH-x$ z^1nM@nkObEk`JXw8!|u^LVkskMI%YiUTu7Ib93_-2JwV;9(@=T$${tben@u69vL)} z^l!Q0_uVHaC%=Xm?Be3Wj*X2aA6AhDcpcA&WQNRZHUphp~SAT zo}QlMgDa{D-p1p2JtQk+jm#NIYDQaITNn5D_d%v(FW}_lWbz>vl>$%W?VxOsEiz^# z$)n%-L-#Z7cZ?MHRG`icQHhQVyd0VbGDWtGBn7M^KKP=X;7vRl!|P;5T^CaBaznmfg?<kPRbAMQU|*b>#T?_!o>BgY)xqJ3T$E16t&IAmcH- z7R$F}fGikED%qu!&ssUrVJw4TMBoQ4vg|}k)6wl**BS|Ihd;Y+UJ^T^FMuJ%a zc4}%$Ptd{}yn~1EQcQ2+bv$n*sZFNUef)H5FcO!Smv(V+QC%FOY48f3!8>^ecY4UUT#D1`)p-+czB2! z5_(NtNVI=YJTW{xj3@9$RIlM(JdBr(Bvq{UdD7j2`Wd6grqO873$5222|~@OJs!xz zTX+`l8cC}9+zr>J^jjVwy1ToxJTZ-qjwXL#RUK+ft*Ln)Uc#fAZy_b6EX!WF7NaxQ z*x$qlLjQIvO;aB5w)Ubd3plR;XQR)NYdN;pWk)6)E?c>$tIqlgS$EyTy9+;ZLL;I{xB>W zYCtWh3AM?){&)(=5%eeMfm55f?EjwLU3^E zFKBFtf@o+6nj=~x2zq&;ADqr-c$Kg07q{Bo(d+e$7ftDOI`TnuM*^2{3b$~4Fn=<37(G z+974JSQzV_wOY;c`MkwqvB(G6e+eAH6`a8x93I+lj1|TVVPvKoEpbY`44aGo*#T z*!jBG0I=aQi0ftkNNvm}Sz|4gO2!N`9*>7E@S+pCp(DDYGrD5~wuFsee2(uK1A!n2 zp`Aq++U@r9t5bzxmi2mVv)PPCZyO8-Mga66V`N6FR4T?^-eR$6xm?b&*{o$U8B3*7 zmP{s%3~C&{AXfxS2?(G0j^DV3Yq^JeX@j!]?\\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 100% rename from .dev-assets/syntax-issues/grid-placement-slash.css rename to .dev-assets/syntax-issues/bugs/property-value-slash-issue.css index 94edcaa..9430638 100644 --- a/.dev-assets/syntax-issues/grid-placement-slash.css +++ b/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css @@ -4,6 +4,6 @@ /* Property value slash doesn't have its own syntax highlighting */ .example { - grid-row: 1 / 3; grid-column: 1/-1; + grid-row: 1 / 3; } diff --git a/.dev-assets/syntax-issues/bugs/trailing-selector-issue.css b/.dev-assets/syntax-issues/bugs/trailing-selector-issue.css new file mode 100644 index 0000000..881e3c2 --- /dev/null +++ b/.dev-assets/syntax-issues/bugs/trailing-selector-issue.css @@ -0,0 +1,44 @@ +/* This shows correctly */ +div:focus { + div:focus { + p: all; + margin:0; + + div:focus { + p: all; + margin:0; + + div:focus { + p: all; + margin:0; + } + } + } +} + +/* But selectors trailing unclosed properties do not show correctly */ +div:focus { + + div:focus { + p: all; + margin:0 + + div:focus { + p: all; + margin:0 + + div:focus { + p: all; + margin:0 + } + } + } + + .b{font-family: + "Arial", + "Courier New", + monospace, + sans-serif + + div {gap:0} +} 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 100% rename from .dev-assets/get-shared-names.js rename to .dev-assets/util/get-shared-names.js 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..93f2489 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,5 +1,4 @@ -.dev-assets/** -.vscode/** -.vscode-test/** +.dev-assets +.vscode .gitignore -vsc-extension-quickstart.md +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..e830bc1 --- /dev/null +++ b/demo/shared-names-demo.css @@ -0,0 +1,120 @@ +/* + * 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..89e0955 --- /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..0eaea7d --- /dev/null +++ b/demo/unterminated-identifiers.css @@ -0,0 +1,230 @@ +.prop{ --some-custom-property-variable: calc(var(--var) * 2) +} +.prop{ \77\69\64\74\68:100% +} +.prop{ color:disabled +} +.prop{ color:optional +} +.prop{ cursor:-webkit-zoom-in +} +.prop{ custom-property:actives +} +.prop{ width:-moz-min-content +} + +.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{ 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..b5f10fe --- /dev/null +++ b/package-lock.json @@ -0,0 +1,202 @@ +{ + "name": "css-nesting-syntax-highlighting", + "version": "0.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "css-nesting-syntax-highlighting", + "version": "0.4.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..1288079 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "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", + "version": "1.0.0", "icon": "images/css-nesting-syntax-highlighting-logo.png", "publisher": "jacobcassidy", "bugs": { @@ -40,6 +40,12 @@ } ] }, + "devDependencies": { + "cson": "^8.4.0" + }, + "scripts": { + "build": "cson2json src/vscode-css/grammars/css.cson > syntaxes/css.tmLanguage.json" + }, "__metadata": { "id": "dc07370c-e087-43f1-91fe-4f44363dc5d7", "publisherId": "aee9e75c-ea26-4af1-b5e6-815e33288c4b", 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..7725cd3 --- /dev/null +++ b/src/vscode-css/grammars/css.cson @@ -0,0 +1,2356 @@ +'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; + } +} From aae8db34c2411f2c8b33829b04baa5e1fb8d5110 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Wed, 28 May 2025 12:02:50 +0700 Subject: [PATCH 02/31] Add vscode-css-non-refactored branch syntax file --- syntaxes/css.tmLanguage.json | 425 +++++++++-------------------------- 1 file changed, 101 insertions(+), 324 deletions(-) diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index e93902e..8a51f5b 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1,12 +1,8 @@ { - "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", + "name": "CSS", + "fileTypes": ["css", "css.erb"], + "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\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" @@ -15,7 +11,7 @@ "include": "#escapes" }, { - "include": "#combinators" + "include": "#combinator-invalid" }, { "include": "#selector" @@ -25,9 +21,6 @@ }, { "include": "#rule-list" - }, - { - "include": "#nesting-selector" } ], "repository": { @@ -37,53 +30,6 @@ }, "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": ";|(?=$)", @@ -273,7 +219,7 @@ "name": "meta.at-rule.media.body.css", "patterns": [ { - "include": "#nesting-at-rules" + "include": "#rule-list-innards" }, { "include": "$self" @@ -558,7 +504,7 @@ "name": "meta.at-rule.supports.body.css", "patterns": [ { - "include": "#nesting-at-rules" + "include": "#rule-list-innards" }, { "include": "$self" @@ -722,7 +668,7 @@ "name": "meta.at-rule.body.css", "patterns": [ { - "include": "#nesting-at-rules" + "include": "#rule-list-innards" }, { "include": "$self" @@ -756,8 +702,7 @@ "combinators": { "patterns": [ { - "match": "/deep/|>>>", - "name": "invalid.deprecated.combinator.css" + "include": "#combinator-invalid" }, { "match": ">>|>|\\+|~", @@ -765,6 +710,10 @@ } ] }, + "combinator-invalid": { + "match": "/deep/|>>>", + "name": "invalid.deprecated.combinator.css" + }, "commas": { "match": ",", "name": "punctuation.separator.list.comma.css" @@ -784,164 +733,6 @@ }, "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 (?\\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": [ { @@ -1687,6 +1443,51 @@ } ] }, + "property": { + "patterns": [ + { + "match": "(?x) (?~+] # 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": [ { @@ -1870,9 +1646,6 @@ }, "selector-innards": { "patterns": [ - { - "include": "#nesting-selector" - }, { "include": "#comment-block" }, @@ -1894,11 +1667,15 @@ "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" + "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" @@ -1916,7 +1693,7 @@ ] } }, - "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 [!\"'%(*;+~|&] # - 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)", + "match": "(?x)\n(\\.) # Valid class-name\n(\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n )+\n) # Followed by either:\n(?= $ # - End of the line\n | [\\s,.\\#)\\[:{>+~|&] # - Another selector\n | /\\* # - A block comment\n)", "name": "entity.other.attribute-name.class.css" }, { From 11d38d317a2ae17ab2879c2142beb50cf3321e45 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Wed, 28 May 2025 12:09:02 +0700 Subject: [PATCH 03/31] Extension's original 0.4.0 syntax --- syntaxes/css.tmLanguage.json | 425 ++++++++++++++++++++++++++--------- 1 file changed, 324 insertions(+), 101 deletions(-) diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index 8a51f5b..e93902e 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1,8 +1,12 @@ { - "scopeName": "source.css", + "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", - "fileTypes": ["css", "css.erb"], - "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\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|:|$)", + "scopeName": "source.css", "patterns": [ { "include": "#comment-block" @@ -11,7 +15,7 @@ "include": "#escapes" }, { - "include": "#combinator-invalid" + "include": "#combinators" }, { "include": "#selector" @@ -21,6 +25,9 @@ }, { "include": "#rule-list" + }, + { + "include": "#nesting-selector" } ], "repository": { @@ -30,6 +37,53 @@ }, "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": ";|(?=$)", @@ -219,7 +273,7 @@ "name": "meta.at-rule.media.body.css", "patterns": [ { - "include": "#rule-list-innards" + "include": "#nesting-at-rules" }, { "include": "$self" @@ -504,7 +558,7 @@ "name": "meta.at-rule.supports.body.css", "patterns": [ { - "include": "#rule-list-innards" + "include": "#nesting-at-rules" }, { "include": "$self" @@ -668,7 +722,7 @@ "name": "meta.at-rule.body.css", "patterns": [ { - "include": "#rule-list-innards" + "include": "#nesting-at-rules" }, { "include": "$self" @@ -702,7 +756,8 @@ "combinators": { "patterns": [ { - "include": "#combinator-invalid" + "match": "/deep/|>>>", + "name": "invalid.deprecated.combinator.css" }, { "match": ">>|>|\\+|~", @@ -710,10 +765,6 @@ } ] }, - "combinator-invalid": { - "match": "/deep/|>>>", - "name": "invalid.deprecated.combinator.css" - }, "commas": { "match": ",", "name": "punctuation.separator.list.comma.css" @@ -733,6 +784,164 @@ }, "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 (?\\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": [ { @@ -1443,51 +1687,6 @@ } ] }, - "property": { - "patterns": [ - { - "match": "(?x) (?~+] # 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)", + "begin": "(?x)\n(?=\n (?:\\|)? # Possible anonymous namespace prefix\n (?:\n [-\\[:.*\\#a-zA-Z_] # Valid selector character\n |\n [^\\x00-\\x7F] # Which can include non-ASCII symbols\n |\n \\\\ # Or an escape sequence\n (?:[0-9a-fA-F]{1,6}|.)\n )\n)", + "end": "(?=\\s*[/@{)])", "name": "meta.selector.css", "patterns": [ { @@ -1646,6 +1870,9 @@ }, "selector-innards": { "patterns": [ + { + "include": "#nesting-selector" + }, { "include": "#comment-block" }, @@ -1667,15 +1894,11 @@ "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" + "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" @@ -1693,7 +1916,7 @@ ] } }, - "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 [!\"'%&(*;+~|&] # - 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)", + "match": "(?x)\n(\\.) # Valid class-name\n(\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n )+\n) # Followed by either:\n(?= $ # - End of the line\n | [\\s,.\\#)\\[:{>+~|&] # - Another selector\n | /\\* # - A block comment\n)", "name": "entity.other.attribute-name.class.css" }, { From c52c9d2da43dff30d36048ad66eef40cdfc2bd05 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Wed, 28 May 2025 12:40:43 +0700 Subject: [PATCH 04/31] Passes tests --- package-lock.json | 4 +- src/vscode-css/grammars/css.cson | 536 ++++++++++++++++--------------- syntaxes/css.tmLanguage.json | 64 +--- 3 files changed, 287 insertions(+), 317 deletions(-) diff --git a/package-lock.json b/package-lock.json index b5f10fe..511e1db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "css-nesting-syntax-highlighting", - "version": "0.4.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "css-nesting-syntax-highlighting", - "version": "0.4.0", + "version": "1.0.0", "devDependencies": { "cson": "^8.4.0" }, diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 7725cd3..184e52d 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -23,7 +23,7 @@ 'include': '#escapes' } { - 'include': '#combinator-invalid' + 'include': '#combinators' } { 'include': '#selector' @@ -34,6 +34,9 @@ { 'include': '#rule-list' } + { + 'include': '#nesting-selector' + } ] 'repository': 'arithmetic-operators': @@ -218,7 +221,7 @@ 'name': 'meta.at-rule.media.body.css' 'patterns': [ { - 'include': '#rule-list-innards' + 'include': '#nesting-at-rules' } { 'include': '$self' @@ -480,9 +483,6 @@ 'name': 'punctuation.section.supports.end.bracket.curly.css' 'name': 'meta.at-rule.supports.body.css' 'patterns': [ - { - 'include': '#rule-list-innards' - } { 'include': '$self' } @@ -642,7 +642,7 @@ 'name': 'meta.at-rule.body.css' 'patterns': [ { - 'include': '#rule-list-innards' + 'include': '#nesting-at-rules' } { 'include': '$self' @@ -655,12 +655,12 @@ 'color-keywords': 'patterns': [ { - # CSS 2.1 colors: http://www.w3.org/TR/CSS21/syndata.html#value-def-color + # CSS 2.1 colours: http://www.w3.org/TR/CSS21/syndata.html#value-def-color 'match': '(?i)(?>>' + 'name': 'invalid.deprecated.combinator.css' } { 'match': '>>|>|\\+|~' 'name': 'keyword.operator.combinator.css' } ] - 'combinator-invalid': - 'match': '/deep/|>>>' - 'name': 'invalid.deprecated.combinator.css' - 'commas': 'match': ',' 'name': 'punctuation.separator.list.comma.css' @@ -725,6 +722,147 @@ '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 (?\\s,.\\#|&){:\\[]|/\\*|$)' + 'name': 'entity.name.tag.css' + } + { + # Custom properties + 'match': '''(?x) (?~+] # 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: + 'begin': '''(?x) (?= - \\s* # Optional whitespace and one of the following: + (?:\\|)? # Possible anonymous namespace prefix (?: - \\/ # Comment - | @ # At-rule - | { # Opening property list brace - | \\) # Closing function brace (for passing test on `some-edgy-new-function(`) - | $ # End of line + [-\\[:.*\\#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': [ { @@ -1979,6 +2004,9 @@ ] 'selector-innards': 'patterns': [ + { + 'include': '#nesting-selector' + } { 'include': '#comment-block' } @@ -2003,7 +2031,7 @@ [-\\w*]+ \\| (?! - [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match + [-\\[:.*\\#a-zA-Z_] # Make sure there's a selector to match | [^\\x00-\\x7F] ) ) @@ -2021,10 +2049,6 @@ { 'include': '#tag-names' } - { - 'match': '&' - 'name': 'entity.name.tag.nesting.css' - } { 'match': '\\*' 'name': 'entity.name.tag.wildcard.css' @@ -2050,7 +2074,7 @@ # Consists of a hyphen only - # Terminated by either: (?= $ # - End-of-line - | [\\s,.\\#)\\[:{>+~|&] # - Followed by another selector + | [\\s,.\\#)\\[:{>+~|&] # - Followed by another selector | /\\* # - Followed by a block comment ) | @@ -2091,7 +2115,7 @@ )+ ) # Followed by either: (?= $ # - End of the line - | [\\s,.\\#)\\[:{>+~|&] # - Another selector + | [\\s,.\\#)\\[:{>+~|&] # - Another selector | /\\* # - A block comment ) ''' diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index e93902e..caa70c2 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1,12 +1,8 @@ { - "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", + "name": "CSS", + "fileTypes": ["css", "css.erb"], + "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\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" @@ -37,53 +33,6 @@ }, "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": ";|(?=$)", @@ -557,9 +506,6 @@ }, "name": "meta.at-rule.supports.body.css", "patterns": [ - { - "include": "#nesting-at-rules" - }, { "include": "$self" } @@ -1073,7 +1019,7 @@ ] }, { - "begin": "(?i)(?+~|&] # - 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 [!\"'%(*;+~|&] # - 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 [!\"'%&(*; Date: Wed, 28 May 2025 12:51:19 +0700 Subject: [PATCH 05/31] Passes tests starting point --- src/vscode-css/grammars/css.cson | 4 ++-- syntaxes/css.tmLanguage.json | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 184e52d..c747d6f 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -983,7 +983,7 @@ } # Colours { - 'begin': '(?i)(?+~|&] # - 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 [!\"'%&(*;+~|&] # - 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 [!\"'%(*; Date: Wed, 28 May 2025 13:52:10 +0700 Subject: [PATCH 06/31] Official code starting point --- src/vscode-css/grammars/css.cson | 257 +- syntaxes/css.tmLanguage.json | 3982 ++++++++++++++---------------- 2 files changed, 1871 insertions(+), 2368 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index c747d6f..6256e55 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -34,14 +34,8 @@ { 'include': '#rule-list' } - { - 'include': '#nesting-selector' - } ] 'repository': - 'arithmetic-operators': - 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' - 'name': 'keyword.operator.arithmetic.css' 'at-rules': 'patterns': [ { @@ -220,9 +214,6 @@ 'name': 'punctuation.section.media.end.bracket.curly.css' 'name': 'meta.at-rule.media.body.css' 'patterns': [ - { - 'include': '#nesting-at-rules' - } { 'include': '$self' } @@ -641,9 +632,6 @@ 'name': 'punctuation.section.end.bracket.curly.css' 'name': 'meta.at-rule.body.css' 'patterns': [ - { - 'include': '#nesting-at-rules' - } { 'include': '$self' } @@ -722,147 +710,6 @@ '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 (?\\s,.\\#|&){:\\[]|/\\*|$)' - 'name': 'entity.name.tag.css' - } - { - # Custom properties - 'match': '''(?x) (?+~|&] # - Followed by another selector + | [\\s,.\\#)\\[:{>+~|] # - Followed by another selector | /\\* # - Followed by a block comment ) | @@ -2084,7 +1843,7 @@ | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence )* (?: # Invalid punctuation - [!"'%(*;+~|&] # - Another selector + | [\\s,.\\#)\\[:{>+~|] # - Another selector | /\\* # - A block comment ) ''' @@ -2138,7 +1897,7 @@ (?![0-9]) (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ ) - (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) + (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) ''' 'name': 'entity.other.attribute-name.id.css' } @@ -2341,7 +2100,7 @@ | mrow|ms|mscarries|mscarry|msgroup|msline|mspace|msqrt|msrow|mstack|mstyle|msub|msubsup | msup|mtable|mtd|mtext|mtr|munder|munderover|semantics ) - (?=[+~>\\s,.\\#|&){:\\[]|/\\*|$) + (?=[+~>\\s,.\\#|){:\\[]|/\\*|$) ''' 'name': 'entity.name.tag.css' 'unicode-range': diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index ca82e83..5a898a6 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1,2120 +1,1864 @@ { - "scopeName": "source.css", - "name": "CSS", - "fileTypes": ["css", "css.erb"], - "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\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" - }, - { - "include": "#nesting-selector" - } - ], - "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": "#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" - }, - { - "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": "(?]?\\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": "(? Date: Wed, 28 May 2025 15:09:07 +0700 Subject: [PATCH 07/31] Update from extension --- src/vscode-css/grammars/css.cson | 79 ++++++++++++++++++++++++++------ syntaxes/css.tmLanguage.json | 74 +++++++++++++++++++++++++++--- 2 files changed, 133 insertions(+), 20 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 6256e55..bc95b75 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -34,8 +34,14 @@ { 'include': '#rule-list' } + { + 'include': '#nesting-selector' + } ] 'repository': + 'arithmetic-operators': + 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' + 'name': 'keyword.operator.arithmetic.css' 'at-rules': 'patterns': [ { @@ -214,6 +220,9 @@ 'name': 'punctuation.section.media.end.bracket.curly.css' 'name': 'meta.at-rule.media.body.css' 'patterns': [ + { + 'include': '#rule-list-innards' + } { 'include': '$self' } @@ -632,6 +641,9 @@ 'name': 'punctuation.section.end.bracket.curly.css' 'name': 'meta.at-rule.body.css' 'patterns': [ + { + 'include': '#rule-list-innards' + } { 'include': '$self' } @@ -643,12 +655,12 @@ 'color-keywords': 'patterns': [ { - # CSS 2.1 colours: http://www.w3.org/TR/CSS21/syndata.html#value-def-color + # CSS 2.1 colors: http://www.w3.org/TR/CSS21/syndata.html#value-def-color 'match': '(?i)(?+~|] # - Followed by another selector + | [\\s,.\\#)\\[:{>+~|&] # - Followed by another selector | /\\* # - Followed by a block comment ) | @@ -1843,7 +1896,7 @@ | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence )* (?: # Invalid punctuation - [!"'%&(*;+~|] # - Another selector + | [\\s,.\\#)\\[:{>+~|&] # - Another selector | /\\* # - A block comment ) ''' @@ -1897,7 +1950,7 @@ (?![0-9]) (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ ) - (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) + (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) ''' 'name': 'entity.other.attribute-name.id.css' } @@ -2100,7 +2153,7 @@ | mrow|ms|mscarries|mscarry|msgroup|msline|mspace|msqrt|msrow|mstack|mstyle|msub|msubsup | msup|mtable|mtd|mtext|mtr|munder|munderover|semantics ) - (?=[+~>\\s,.\\#|){:\\[]|/\\*|$) + (?=[+~>\\s,.\\#|&){:\\[]|/\\*|$) ''' 'name': 'entity.name.tag.css' 'unicode-range': diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index 5a898a6..912702a 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -24,9 +24,16 @@ }, { "include": "#rule-list" + }, + { + "include": "#nesting-selector" } ], "repository": { + "arithmetic-operators": { + "match": "[*/]|(?<=\\s|^)[-+](?=\\s|$)", + "name": "keyword.operator.arithmetic.css" + }, "at-rules": { "patterns": [ { @@ -217,6 +224,9 @@ }, "name": "meta.at-rule.media.body.css", "patterns": [ + { + "include": "#rule-list-innards" + }, { "include": "$self" } @@ -660,6 +670,9 @@ }, "name": "meta.at-rule.body.css", "patterns": [ + { + "include": "#rule-list-innards" + }, { "include": "$self" } @@ -840,16 +853,18 @@ "name": "meta.function.calc.css", "patterns": [ { - "match": "[*/]|(?<=\\s|^)[-+](?=\\s|$)", - "name": "keyword.operator.arithmetic.css" + "include": "#arithmetic-operators" }, { "include": "#property-values" + }, + { + "include": "#function-nesting" } ] }, { - "begin": "(?i)(?+~|] # - 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 [!\"'%&(*;+~|&] # - 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)", + "match": "(?x)\n(\\.) # Valid class-name\n(\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n )+\n) # Followed by either:\n(?= $ # - End of the line\n | [\\s,.\\#)\\[:{>+~|&] # - Another selector\n | /\\* # - A block comment\n)", "name": "entity.other.attribute-name.class.css" }, { @@ -1649,7 +1709,7 @@ ] } }, - "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|]|/\\*)", + "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" }, { @@ -1813,7 +1873,7 @@ ] }, "tag-names": { - "match": "(?xi) (?\\s,.\\#|){:\\[]|/\\*|$)", + "match": "(?xi) (?\\s,.\\#|&){:\\[]|/\\*|$)", "name": "entity.name.tag.css" }, "unicode-range": { From eeaa65cbe98a3479f2f3d9052c89a6ed39a3e6bd Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sat, 31 May 2025 10:34:24 +0700 Subject: [PATCH 08/31] Final default `css.cson` --- src/vscode-css/grammars/css.cson | 254 ++++++++++++++++++++++++------- 1 file changed, 202 insertions(+), 52 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index bc95b75..5824660 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -16,6 +16,9 @@ (?=\\s|:|$) ''' 'patterns': [ + { + 'include': '#commas' + } { 'include': '#comment-block' } @@ -23,7 +26,7 @@ 'include': '#escapes' } { - 'include': '#combinators' + 'include': '#combinator-invalid' } { 'include': '#selector' @@ -34,9 +37,6 @@ { 'include': '#rule-list' } - { - 'include': '#nesting-selector' - } ] 'repository': 'arithmetic-operators': @@ -483,6 +483,9 @@ 'name': 'punctuation.section.supports.end.bracket.curly.css' 'name': 'meta.at-rule.supports.body.css' 'patterns': [ + { + 'include': '#rule-list-innards' + } { 'include': '$self' } @@ -701,14 +704,16 @@ 'combinators': 'patterns': [ { - 'match': '/deep/|>>>' - 'name': 'invalid.deprecated.combinator.css' + 'include': '#combinator-invalid' } { 'match': '>>|>|\\+|~' 'name': 'keyword.operator.combinator.css' } ] + 'combinator-invalid': + 'match': '/deep/|>>>' + 'name': 'invalid.deprecated.combinator.css' 'commas': 'match': ',' 'name': 'punctuation.separator.list.comma.css' @@ -1068,27 +1073,23 @@ } ] 'function-nesting': + 'begin': '\\(' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.function.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.function.end.bracket.round.css' 'patterns': [ { - 'begin': '\\(' - 'beginCaptures': - '0': - 'name': 'punctuation.definition.begin.bracket.round.scss' - 'end': '\\)' - 'endCaptures': - '0': - 'name': 'punctuation.definition.end.bracket.round.scss' - 'patterns': [ - { - 'include': '#arithmetic-operators' - } - { - 'include': '#property-values' - }, - { - 'include': '#function-nesting' - } - ] + 'include': '#arithmetic-operators' + } + { + 'include': '#property-values' + }, + { + 'include': '#function-nesting' } ] 'functional-pseudo-classes': @@ -1394,7 +1395,7 @@ 'include': '#comment-block' } { - "include": "#functions" + 'include': '#functions' } ] } @@ -1424,9 +1425,6 @@ ) (?=$|[{,\\s;]|/\\*) ''' - 'nesting-selector': - 'match': '&' - 'name': 'entity.name.tag.nesting.selector.css' 'numeric-values': 'patterns': [ { @@ -1629,11 +1627,11 @@ ) (?![\\w-]) ''' - 'name': 'support.type.property-name.css' + 'name': 'meta.property-name.css support.type.property-name.css' } { 'match': '(?~+] # 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 ) ) - ''' - 'end': '(?=\\s*[/@{)])' + + # 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': [ { @@ -1843,7 +1909,7 @@ [-\\w*]+ \\| (?! - [-\\[:.*\\#a-zA-Z_] # Make sure there's a selector to match + [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match | [^\\x00-\\x7F] ) ) @@ -1861,6 +1927,10 @@ { 'include': '#tag-names' } + { + 'match': '&' + 'name': 'entity.name.tag.nesting.css' + } { 'match': '\\*' 'name': 'entity.name.tag.wildcard.css' @@ -2065,6 +2135,85 @@ 'name': 'entity.name.tag.custom.css' } ] + 'shared-names': + # Shared names are keywords that can be used as selector or property names. + # If the following conditions are met, the keyword is considered a selector: + 'begin': '''(?xi) + # Selector match must be preceded by one of the following: + (?<= + ^ # Start of line + | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon + | [{}] # Opening or closing brace (condensed property list syntax) + | \\*/ # Comment end + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + ) + + (?= + # Selector must match: + (?: + # HTML elements + content|font|header|image|label|mark|marquee|mask|nav|ruby|shadow|span|style + # SVG elements + |color-profile|cursor|filter|line|text + ) + + # Selector must NOT be followed by any any of the following: + (?! + .*; # Any characters followed by a semicolon (denotes a property) + | .*} # Any characters followed by a closing bracket (denotes a property) + | - # A dash (denotes a property name) + # | \\s*\\: # A colon, unless it's with one of the following: + # (?! + # # A opening bracket before an closing bracket + # [^}]*{ + # # A pseudo-class selectors + # | active|any-link|checked|disabled|empty|enabled|first + # | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover + # | in-range|indeterminate|invalid|link|out-of-range + # | read-only|read-write|required|root|scope|target|unresolved + # | valid|visited + + # # A functional pseudo-class selectors + # | (?: dir|lang + # | not|has|matches|where|is + # | nth-(?:last-)?(?:child|of-type) + # )\\( + + # # A single-colon pseudo-element selectors + # | 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-]+ + # ) + ) + ) + ''' + 'end': '''(?xi) + # Selector match ends with one of the following: + (?= + \\s # Whitespace + | \\/\\* # Comment + | , # Comma + | { # Opening property list brace + | $ # End of line + ) + ''' + 'patterns': [ + { + 'include': '#selector' + } + ] 'string': 'patterns': [ { @@ -2190,3 +2339,4 @@ 'include': '#escapes' } ] + From a3cb480898723d6fbaa67205bfd8e95bf7ebadcb Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sat, 31 May 2025 10:36:03 +0700 Subject: [PATCH 09/31] Final default `css.tmLanguage.json` --- syntaxes/css.tmLanguage.json | 106 +++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index 912702a..a7628f7 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -7,6 +7,9 @@ ], "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\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": "#commas" + }, { "include": "#comment-block" }, @@ -14,7 +17,7 @@ "include": "#escapes" }, { - "include": "#combinators" + "include": "#combinator-invalid" }, { "include": "#selector" @@ -24,9 +27,6 @@ }, { "include": "#rule-list" - }, - { - "include": "#nesting-selector" } ], "repository": { @@ -509,6 +509,9 @@ }, "name": "meta.at-rule.supports.body.css", "patterns": [ + { + "include": "#rule-list-innards" + }, { "include": "$self" } @@ -705,8 +708,7 @@ "combinators": { "patterns": [ { - "match": "/deep/|>>>", - "name": "invalid.deprecated.combinator.css" + "include": "#combinator-invalid" }, { "match": ">>|>|\\+|~", @@ -714,6 +716,10 @@ } ] }, + "combinator-invalid": { + "match": "/deep/|>>>", + "name": "invalid.deprecated.combinator.css" + }, "commas": { "match": ",", "name": "punctuation.separator.list.comma.css" @@ -1101,31 +1107,27 @@ ] }, "function-nesting": { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.function.begin.bracket.round.css" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.function.end.bracket.round.css" + } + }, "patterns": [ { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.begin.bracket.round.scss" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.end.bracket.round.scss" - } - }, - "patterns": [ - { - "include": "#arithmetic-operators" - }, - { - "include": "#property-values" - }, - { - "include": "#function-nesting" - } - ] + "include": "#arithmetic-operators" + }, + { + "include": "#property-values" + }, + { + "include": "#function-nesting" } ] }, @@ -1422,10 +1424,6 @@ }, "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-selector": { - "match": "&", - "name": "entity.name.tag.nesting.selector.css" - }, "numeric-values": { "patterns": [ { @@ -1475,11 +1473,11 @@ "patterns": [ { "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": [ { @@ -1655,11 +1654,15 @@ "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" + "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" @@ -1808,6 +1811,15 @@ } ] }, + "shared-names": { + "begin": "(?xi)\n# Selector match must be preceded by one of the following:\n(?<=\n ^ # Start of line\n | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon\n | [{}] # Opening or closing brace (condensed property list syntax)\n | \\*/ # Comment end\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n)\n\n(?=\n # Selector must match:\n (?:\n # HTML elements\n content|font|header|image|label|mark|marquee|mask|nav|ruby|shadow|span|style\n # SVG elements\n |color-profile|cursor|filter|line|text\n )\n\n # Selector must NOT be followed by any any of the following:\n (?!\n .*; # Any characters followed by a semicolon (denotes a property)\n | .*} # Any characters followed by a closing bracket (denotes a property)\n | - # A dash (denotes a property name)\n # | \\s*\\: # A colon, unless it's with one of the following:\n # (?!\n # # A opening bracket before an closing bracket\n # [^}]*{\n # # A pseudo-class selectors\n # | active|any-link|checked|disabled|empty|enabled|first\n # | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover\n # | in-range|indeterminate|invalid|link|out-of-range\n # | read-only|read-write|required|root|scope|target|unresolved\n # | valid|visited\n\n # # A functional pseudo-class selectors\n # | (?: dir|lang\n # | not|has|matches|where|is\n # | nth-(?:last-)?(?:child|of-type)\n # )\\(\n\n # # A single-colon pseudo-element selectors\n # | after\n # | before\n # | first-letter\n # | first-line\n # | (?:\n # \\-\n # (?:\n # ah|apple|atsc|epub|hp|khtml|moz\n # | ms|o|rim|ro|tc|wap|webkit|xv\n # )\n # | (?:\n # mso|prince\n # )\n # )\n # -[a-z-]+\n # )\n )\n)", + "end": "(?xi)\n# Selector match ends with one of the following:\n(?=\n \\s # Whitespace\n | \\/\\* # Comment\n | , # Comma\n | { # Opening property list brace\n | $ # End of line\n)", + "patterns": [ + { + "include": "#selector" + } + ] + }, "string": { "patterns": [ { From cf3260a0d66e489c22af3e310f9b4ef5ab47c24b Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 09:06:10 +0700 Subject: [PATCH 10/31] Update shared names rules --- src/vscode-css/grammars/css.cson | 189 +++++++++++++++++++------------ syntaxes/css.tmLanguage.json | 19 +++- 2 files changed, 135 insertions(+), 73 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 5824660..a8cbb26 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -2136,82 +2136,131 @@ } ] 'shared-names': - # Shared names are keywords that can be used as selector or property names. - # If the following conditions are met, the keyword is considered a selector: - 'begin': '''(?xi) - # Selector match must be preceded by one of the following: - (?<= - ^ # Start of line - | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon - | [{}] # Opening or closing brace (condensed property list syntax) - | \\*/ # Comment end - | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence - ) + # Shared names are keywords that are listed in both selector and property name contexts. + 'patterns': [ + # The following are considered selectors by default: + { + 'begin': '''(?xi) + # Selector match must be preceded by one of the following: + (?<= + ^ # Start of line + | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon + | [{}] # Opening or closing brace (condensed property list syntax) + | \\*/ # Comment end + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + ) - (?= - # Selector must match: - (?: - # HTML elements - content|font|header|image|label|mark|marquee|mask|nav|ruby|shadow|span|style - # SVG elements - |color-profile|cursor|filter|line|text + (?= + # Selector must match: + (?: + # HTML elements + header|image|label|marquee|mask|nav|ruby|shadow|span|style + # SVG elements + |color-profile|line|text + ) + + # Selector must NOT be followed by any any of the following: + (?! + .*; # Any characters followed by a semicolon (denotes a property) + | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property) + | - # A dash (denotes a property name) + ) + ) + ''' + 'end': '''(?xi) + # Selector match ends with one of the following: + (?= + \\s # Whitespace + | \\/\\* # Comment + | , # Comma + | { # Opening property list brace + | $ # End of line + ) + ''' + 'patterns': [ + { + 'include': '#selector' + } + ] + } + # The following are considered property names by default when attached to a colon: + { + 'begin': '''(?xi) + # Selector match must be preceded by one of the following: + (?<= + ^ # Start of line + | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon + | [{}] # Opening or closing brace (condensed property list syntax) + | \\*/ # Comment end + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence ) - # Selector must NOT be followed by any any of the following: - (?! - .*; # Any characters followed by a semicolon (denotes a property) - | .*} # Any characters followed by a closing bracket (denotes a property) - | - # A dash (denotes a property name) - # | \\s*\\: # A colon, unless it's with one of the following: - # (?! - # # A opening bracket before an closing bracket - # [^}]*{ - # # A pseudo-class selectors - # | active|any-link|checked|disabled|empty|enabled|first - # | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover - # | in-range|indeterminate|invalid|link|out-of-range - # | read-only|read-write|required|root|scope|target|unresolved - # | valid|visited + (?= + # Selector must match: + (?: + # HTML elements + content|font|mark + # SVG elements + |cursor|filter + ) - # # A functional pseudo-class selectors - # | (?: dir|lang - # | not|has|matches|where|is - # | nth-(?:last-)?(?:child|of-type) - # )\\( + # Selector must NOT be followed by any any of the following: + (?! + .*; # Any characters followed by a semicolon (denotes a property) + | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property) + | - # A dash (denotes a property name) + | \\: # A colon, unless it's with one of the following: + (?! + # A opening bracket before an closing bracket + [^}]*{ + # A pseudo-class selectors + | active|any-link|checked|disabled|empty|enabled|first + | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover + | in-range|indeterminate|invalid|link|out-of-range + | read-only|read-write|required|root|scope|target|unresolved + | valid|visited - # # A single-colon pseudo-element selectors - # | 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-]+ - # ) + # A functional pseudo-class selectors + | (?: dir|lang + | not|has|matches|where|is + | nth-(?:last-)?(?:child|of-type) + )\\( + + # A single-colon pseudo-element selectors + | 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-]+ + ) + ) ) - ) - ''' - 'end': '''(?xi) - # Selector match ends with one of the following: - (?= - \\s # Whitespace - | \\/\\* # Comment - | , # Comma - | { # Opening property list brace - | $ # End of line - ) - ''' - 'patterns': [ - { - 'include': '#selector' + ''' + 'end': '''(?xi) + # Selector match ends with one of the following: + (?= + \\s # Whitespace + | \\/\\* # Comment + | , # Comma + | { # Opening property list brace + | $ # End of line + ) + ''' + 'patterns': [ + { + 'include': '#selector' + } + ] } ] 'string': diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index a7628f7..ff8d183 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1812,11 +1812,24 @@ ] }, "shared-names": { - "begin": "(?xi)\n# Selector match must be preceded by one of the following:\n(?<=\n ^ # Start of line\n | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon\n | [{}] # Opening or closing brace (condensed property list syntax)\n | \\*/ # Comment end\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n)\n\n(?=\n # Selector must match:\n (?:\n # HTML elements\n content|font|header|image|label|mark|marquee|mask|nav|ruby|shadow|span|style\n # SVG elements\n |color-profile|cursor|filter|line|text\n )\n\n # Selector must NOT be followed by any any of the following:\n (?!\n .*; # Any characters followed by a semicolon (denotes a property)\n | .*} # Any characters followed by a closing bracket (denotes a property)\n | - # A dash (denotes a property name)\n # | \\s*\\: # A colon, unless it's with one of the following:\n # (?!\n # # A opening bracket before an closing bracket\n # [^}]*{\n # # A pseudo-class selectors\n # | active|any-link|checked|disabled|empty|enabled|first\n # | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover\n # | in-range|indeterminate|invalid|link|out-of-range\n # | read-only|read-write|required|root|scope|target|unresolved\n # | valid|visited\n\n # # A functional pseudo-class selectors\n # | (?: dir|lang\n # | not|has|matches|where|is\n # | nth-(?:last-)?(?:child|of-type)\n # )\\(\n\n # # A single-colon pseudo-element selectors\n # | after\n # | before\n # | first-letter\n # | first-line\n # | (?:\n # \\-\n # (?:\n # ah|apple|atsc|epub|hp|khtml|moz\n # | ms|o|rim|ro|tc|wap|webkit|xv\n # )\n # | (?:\n # mso|prince\n # )\n # )\n # -[a-z-]+\n # )\n )\n)", - "end": "(?xi)\n# Selector match ends with one of the following:\n(?=\n \\s # Whitespace\n | \\/\\* # Comment\n | , # Comma\n | { # Opening property list brace\n | $ # End of line\n)", "patterns": [ { - "include": "#selector" + "begin": "(?xi)\n# Selector match must be preceded by one of the following:\n(?<=\n ^ # Start of line\n | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon\n | [{}] # Opening or closing brace (condensed property list syntax)\n | \\*/ # Comment end\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n)\n\n(?=\n # Selector must match:\n (?:\n # HTML elements\n header|image|label|marquee|mask|nav|ruby|shadow|span|style\n # SVG elements\n |color-profile|line|text\n )\n\n # Selector must NOT be followed by any any of the following:\n (?!\n .*; # Any characters followed by a semicolon (denotes a property)\n | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property)\n | - # A dash (denotes a property name)\n )\n)", + "end": "(?xi)\n# Selector match ends with one of the following:\n(?=\n \\s # Whitespace\n | \\/\\* # Comment\n | , # Comma\n | { # Opening property list brace\n | $ # End of line\n)", + "patterns": [ + { + "include": "#selector" + } + ] + }, + { + "begin": "(?xi)\n# Selector match must be preceded by one of the following:\n(?<=\n ^ # Start of line\n | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon\n | [{}] # Opening or closing brace (condensed property list syntax)\n | \\*/ # Comment end\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n)\n\n(?=\n # Selector must match:\n (?:\n # HTML elements\n content|font|mark\n # SVG elements\n |cursor|filter\n )\n\n # Selector must NOT be followed by any any of the following:\n (?!\n .*; # Any characters followed by a semicolon (denotes a property)\n | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property)\n | - # A dash (denotes a property name)\n | \\: # A colon, unless it's with one of the following:\n (?!\n # A opening bracket before an closing bracket\n [^}]*{\n # A pseudo-class selectors\n | active|any-link|checked|disabled|empty|enabled|first\n | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover\n | in-range|indeterminate|invalid|link|out-of-range\n | read-only|read-write|required|root|scope|target|unresolved\n | valid|visited\n\n # A functional pseudo-class selectors\n | (?: dir|lang\n | not|has|matches|where|is\n | nth-(?:last-)?(?:child|of-type)\n )\\(\n\n # A single-colon pseudo-element selectors\n | after\n | before\n | first-letter\n | first-line\n | (?:\n \\-\n (?:\n ah|apple|atsc|epub|hp|khtml|moz\n | ms|o|rim|ro|tc|wap|webkit|xv\n )\n | (?:\n mso|prince\n )\n )\n -[a-z-]+\n )\n )\n)", + "end": "(?xi)\n# Selector match ends with one of the following:\n(?=\n \\s # Whitespace\n | \\/\\* # Comment\n | , # Comma\n | { # Opening property list brace\n | $ # End of line\n)", + "patterns": [ + { + "include": "#selector" + } + ] } ] }, From 60ef6882b0a35ff24c178593327538051866ec61 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 09:06:28 +0700 Subject: [PATCH 11/31] Update shared name examples --- .../bugs/trailing-selector-issue.css | 44 ------------------- .dev-assets/util/get-shared-names.js | 2 +- demo/shared-names-demo.css | 1 + demo/unterminated-identifiers.css | 9 ++-- 4 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 .dev-assets/syntax-issues/bugs/trailing-selector-issue.css diff --git a/.dev-assets/syntax-issues/bugs/trailing-selector-issue.css b/.dev-assets/syntax-issues/bugs/trailing-selector-issue.css deleted file mode 100644 index 881e3c2..0000000 --- a/.dev-assets/syntax-issues/bugs/trailing-selector-issue.css +++ /dev/null @@ -1,44 +0,0 @@ -/* This shows correctly */ -div:focus { - div:focus { - p: all; - margin:0; - - div:focus { - p: all; - margin:0; - - div:focus { - p: all; - margin:0; - } - } - } -} - -/* But selectors trailing unclosed properties do not show correctly */ -div:focus { - - div:focus { - p: all; - margin:0 - - div:focus { - p: all; - margin:0 - - div:focus { - p: all; - margin:0 - } - } - } - - .b{font-family: - "Arial", - "Courier New", - monospace, - sans-serif - - div {gap:0} -} diff --git a/.dev-assets/util/get-shared-names.js b/.dev-assets/util/get-shared-names.js index 729219a..2a52701 100644 --- a/.dev-assets/util/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/demo/shared-names-demo.css b/demo/shared-names-demo.css index e830bc1..9a66b05 100644 --- a/demo/shared-names-demo.css +++ b/demo/shared-names-demo.css @@ -5,6 +5,7 @@ .shared-names { /* HTML Selectors */ + content { content:url('https://www.example.com') diff --git a/demo/unterminated-identifiers.css b/demo/unterminated-identifiers.css index 0eaea7d..67bfa55 100644 --- a/demo/unterminated-identifiers.css +++ b/demo/unterminated-identifiers.css @@ -6,13 +6,14 @@ } .prop{ color:optional } -.prop{ cursor:-webkit-zoom-in +.prop{ cursor:anything } -.prop{ custom-property:actives +.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) @@ -77,6 +78,8 @@ } .prop{ color:var(--primary-color) } +.prop{ span:"" +} .prop{ content:"" } .prop{ content:"hello" From 5cc546825ae616a4c8a1a84890fc64cb85f8a855 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 09:32:48 +0700 Subject: [PATCH 12/31] Refactor package organization --- package.json | 70 ++++++++++++++++++++++------------------------------ 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 1288079..20cd885 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,11 @@ { "name": "css-nesting-syntax-highlighting", "displayName": "CSS Nesting Syntax Highlighting", - "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", "version": "1.0.0", - "icon": "images/css-nesting-syntax-highlighting-logo.png", "publisher": "jacobcassidy", - "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" - }, - "galleryBanner": { - "color": "#292929", - "theme": "dark" - }, - "keywords": [ - "CSS Nesting", - "Nested CSS", - "Nesting", - "Nested", - "Syntax", - "Highlighting", - "Colorizer" - ], - "engines": { - "vscode": "^1.84.0" + "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", + "author": { + "name": "Jacob Cassidy" }, "categories": [ "Programming Languages" @@ -35,30 +14,39 @@ "grammars": [ { "language": "css", - "scopeName": "source.css", - "path": "./syntaxes/css.tmLanguage.json" + "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", + "Syntax" + ], "devDependencies": { "cson": "^8.4.0" }, + "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" + }, "scripts": { "build": "cson2json src/vscode-css/grammars/css.cson > syntaxes/css.tmLanguage.json" - }, - "__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" } } From 344aca433baf848d59b06ee6d967453cd98a2bb2 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 09:33:01 +0700 Subject: [PATCH 13/31] Update .vscodeignore --- .vscodeignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscodeignore b/.vscodeignore index 93f2489..99ad984 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,4 +1,9 @@ .dev-assets -.vscode .gitignore +.package-lock.json +.prettierignore +.prettierrc +.vscode +.vscodeignore +demo src From e248c67e83eeb63f60891d9e508802216108f9de Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 09:33:50 +0700 Subject: [PATCH 14/31] Ignore node_modules --- .vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscodeignore b/.vscodeignore index 99ad984..e2b2105 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -6,4 +6,5 @@ .vscode .vscodeignore demo +node_modules src From 75f6940ecb50c813cef7a08b50ff8c1648305b6a Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:09:22 +0700 Subject: [PATCH 15/31] Reset `css.cson` of official original file --- src/vscode-css/grammars/css.cson | 328 ++++--------------------------- 1 file changed, 38 insertions(+), 290 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index a8cbb26..6256e55 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -16,9 +16,6 @@ (?=\\s|:|$) ''' 'patterns': [ - { - 'include': '#commas' - } { 'include': '#comment-block' } @@ -26,7 +23,7 @@ 'include': '#escapes' } { - 'include': '#combinator-invalid' + 'include': '#combinators' } { 'include': '#selector' @@ -39,9 +36,6 @@ } ] 'repository': - 'arithmetic-operators': - 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' - 'name': 'keyword.operator.arithmetic.css' 'at-rules': 'patterns': [ { @@ -220,9 +214,6 @@ 'name': 'punctuation.section.media.end.bracket.curly.css' 'name': 'meta.at-rule.media.body.css' 'patterns': [ - { - 'include': '#rule-list-innards' - } { 'include': '$self' } @@ -483,9 +474,6 @@ 'name': 'punctuation.section.supports.end.bracket.curly.css' 'name': 'meta.at-rule.supports.body.css' 'patterns': [ - { - 'include': '#rule-list-innards' - } { 'include': '$self' } @@ -644,9 +632,6 @@ 'name': 'punctuation.section.end.bracket.curly.css' 'name': 'meta.at-rule.body.css' 'patterns': [ - { - 'include': '#rule-list-innards' - } { 'include': '$self' } @@ -658,12 +643,12 @@ 'color-keywords': 'patterns': [ { - # CSS 2.1 colors: http://www.w3.org/TR/CSS21/syndata.html#value-def-color + # CSS 2.1 colours: http://www.w3.org/TR/CSS21/syndata.html#value-def-color 'match': '(?i)(?>>' + 'name': 'invalid.deprecated.combinator.css' } { 'match': '>>|>|\\+|~' 'name': 'keyword.operator.combinator.css' } ] - 'combinator-invalid': - 'match': '/deep/|>>>' - 'name': 'invalid.deprecated.combinator.css' 'commas': 'match': ',' 'name': 'punctuation.separator.list.comma.css' @@ -835,19 +818,17 @@ 'name': 'meta.function.calc.css' 'patterns': [ { - 'include': '#arithmetic-operators' + 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' + 'name': 'keyword.operator.arithmetic.css' } { 'include': '#property-values' } - { - 'include': '#function-nesting' - } ] } - # colors + # Colours { - 'begin': '(?i)(?~+] # 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 + [-\\[:.*\\#a-zA-Z_] # Valid selector character + | + [^\\x00-\\x7F] # Which can include non-ASCII symbols + | + \\\\ # Or an escape sequence + (?:[0-9a-fA-F]{1,6}|.) ) ) - - # 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 - ) - ) - ''' + ''' + 'end': '(?=\\s*[/@{)])' 'name': 'meta.selector.css' 'patterns': [ { @@ -1909,7 +1790,7 @@ [-\\w*]+ \\| (?! - [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match + [-\\[:.*\\#a-zA-Z_] # Make sure there's a selector to match | [^\\x00-\\x7F] ) ) @@ -1927,10 +1808,6 @@ { 'include': '#tag-names' } - { - 'match': '&' - 'name': 'entity.name.tag.nesting.css' - } { 'match': '\\*' 'name': 'entity.name.tag.wildcard.css' @@ -1956,7 +1833,7 @@ # Consists of a hyphen only - # Terminated by either: (?= $ # - End-of-line - | [\\s,.\\#)\\[:{>+~|&] # - Followed by another selector + | [\\s,.\\#)\\[:{>+~|] # - Followed by another selector | /\\* # - Followed by a block comment ) | @@ -1966,7 +1843,7 @@ | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence )* (?: # Invalid punctuation - [!"'%(*;+~|&] # - Another selector + | [\\s,.\\#)\\[:{>+~|] # - Another selector | /\\* # - A block comment ) ''' @@ -2020,7 +1897,7 @@ (?![0-9]) (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ ) - (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) + (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) ''' 'name': 'entity.other.attribute-name.id.css' } @@ -2135,134 +2012,6 @@ 'name': 'entity.name.tag.custom.css' } ] - 'shared-names': - # Shared names are keywords that are listed in both selector and property name contexts. - 'patterns': [ - # The following are considered selectors by default: - { - 'begin': '''(?xi) - # Selector match must be preceded by one of the following: - (?<= - ^ # Start of line - | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon - | [{}] # Opening or closing brace (condensed property list syntax) - | \\*/ # Comment end - | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence - ) - - (?= - # Selector must match: - (?: - # HTML elements - header|image|label|marquee|mask|nav|ruby|shadow|span|style - # SVG elements - |color-profile|line|text - ) - - # Selector must NOT be followed by any any of the following: - (?! - .*; # Any characters followed by a semicolon (denotes a property) - | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property) - | - # A dash (denotes a property name) - ) - ) - ''' - 'end': '''(?xi) - # Selector match ends with one of the following: - (?= - \\s # Whitespace - | \\/\\* # Comment - | , # Comma - | { # Opening property list brace - | $ # End of line - ) - ''' - 'patterns': [ - { - 'include': '#selector' - } - ] - } - # The following are considered property names by default when attached to a colon: - { - 'begin': '''(?xi) - # Selector match must be preceded by one of the following: - (?<= - ^ # Start of line - | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon - | [{}] # Opening or closing brace (condensed property list syntax) - | \\*/ # Comment end - | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence - ) - - (?= - # Selector must match: - (?: - # HTML elements - content|font|mark - # SVG elements - |cursor|filter - ) - - # Selector must NOT be followed by any any of the following: - (?! - .*; # Any characters followed by a semicolon (denotes a property) - | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property) - | - # A dash (denotes a property name) - | \\: # A colon, unless it's with one of the following: - (?! - # A opening bracket before an closing bracket - [^}]*{ - # A pseudo-class selectors - | active|any-link|checked|disabled|empty|enabled|first - | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover - | in-range|indeterminate|invalid|link|out-of-range - | read-only|read-write|required|root|scope|target|unresolved - | valid|visited - - # A functional pseudo-class selectors - | (?: dir|lang - | not|has|matches|where|is - | nth-(?:last-)?(?:child|of-type) - )\\( - - # A single-colon pseudo-element selectors - | 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-]+ - ) - ) - ) - ''' - 'end': '''(?xi) - # Selector match ends with one of the following: - (?= - \\s # Whitespace - | \\/\\* # Comment - | , # Comma - | { # Opening property list brace - | $ # End of line - ) - ''' - 'patterns': [ - { - 'include': '#selector' - } - ] - } - ] 'string': 'patterns': [ { @@ -2351,7 +2100,7 @@ | mrow|ms|mscarries|mscarry|msgroup|msline|mspace|msqrt|msrow|mstack|mstyle|msub|msubsup | msup|mtable|mtd|mtext|mtr|munder|munderover|semantics ) - (?=[+~>\\s,.\\#|&){:\\[]|/\\*|$) + (?=[+~>\\s,.\\#|){:\\[]|/\\*|$) ''' 'name': 'entity.name.tag.css' 'unicode-range': @@ -2388,4 +2137,3 @@ 'include': '#escapes' } ] - From fa70ca172d3158c66ba9848a76311321a46652d5 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:16:10 +0700 Subject: [PATCH 16/31] Add `#rule-list` nesting --- src/vscode-css/grammars/css.cson | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 6256e55..e659200 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1686,6 +1686,9 @@ { 'include': '#rule-list-innards' } + { + 'include': '$self' + } ] 'rule-list-innards': 'patterns': [ From 7057054f32f3a5bac1f6e947f1de95d591e3696b Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:26:35 +0700 Subject: [PATCH 17/31] Add `&` nesting selector --- src/vscode-css/grammars/css.cson | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index e659200..aab513b 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1793,7 +1793,7 @@ [-\\w*]+ \\| (?! - [-\\[:.*\\#a-zA-Z_] # Make sure there's a selector to match + [-\\[:.*&\\#a-zA-Z_] # Make sure there's a selector to match | [^\\x00-\\x7F] ) ) @@ -1811,6 +1811,10 @@ { 'include': '#tag-names' } + { + 'match': '&' + 'name': 'entity.name.tag.nesting.css' + } { 'match': '\\*' 'name': 'entity.name.tag.wildcard.css' @@ -1836,7 +1840,7 @@ # Consists of a hyphen only - # Terminated by either: (?= $ # - End-of-line - | [\\s,.\\#)\\[:{>+~|] # - Followed by another selector + | [\\s,.\\#)\\[:{>+~|&] # - Followed by another selector | /\\* # - Followed by a block comment ) | @@ -1846,7 +1850,7 @@ | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence )* (?: # Invalid punctuation - [!"'%&(*;+~|] # - Another selector + | [\\s,.\\#)\\[:{>+~|&] # - Another selector | /\\* # - A block comment ) ''' @@ -1900,7 +1904,7 @@ (?![0-9]) (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ ) - (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) + (?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*) ''' 'name': 'entity.other.attribute-name.id.css' } @@ -2103,7 +2107,7 @@ | mrow|ms|mscarries|mscarry|msgroup|msline|mspace|msqrt|msrow|mstack|mstyle|msub|msubsup | msup|mtable|mtd|mtext|mtr|munder|munderover|semantics ) - (?=[+~>\\s,.\\#|){:\\[]|/\\*|$) + (?=[+~>\\s,.\\#|&){:\\[]|/\\*|$) ''' 'name': 'entity.name.tag.css' 'unicode-range': From 159a043452df01b907114f5aa73bdc94002201cd Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:29:44 +0700 Subject: [PATCH 18/31] Add arithmetic operators to functions --- src/vscode-css/grammars/css.cson | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index aab513b..7379e10 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -36,6 +36,9 @@ } ] 'repository': + 'arithmetic-operators': + 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' + 'name': 'keyword.operator.arithmetic.css' 'at-rules': 'patterns': [ { @@ -818,8 +821,7 @@ 'name': 'meta.function.calc.css' 'patterns': [ { - 'match': '[*/]|(?<=\\s|^)[-+](?=\\s|$)' - 'name': 'keyword.operator.arithmetic.css' + 'include': '#arithmetic-operators' } { 'include': '#property-values' @@ -936,6 +938,8 @@ { 'match': '(?i)(?<=[,\\s"]|\\*/|^)\\d+x(?=[\\s,"\')]|/\\*|$)' 'name': 'constant.numeric.other.density.css' + } + 'include': '#arithmetic-operators' } { 'include': '#property-values' From f85aadc3a393541090d552bc391cde2a9b4ec31b Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:36:00 +0700 Subject: [PATCH 19/31] Add `#function-nesting` --- src/vscode-css/grammars/css.cson | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 7379e10..506a89c 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -826,6 +826,9 @@ { 'include': '#property-values' } + { + 'include': '#function-nesting' + } ] } # Colours @@ -939,6 +942,7 @@ 'match': '(?i)(?<=[,\\s"]|\\*/|^)\\d+x(?=[\\s,"\')]|/\\*|$)' 'name': 'constant.numeric.other.density.css' } + { 'include': '#arithmetic-operators' } { @@ -1054,6 +1058,26 @@ ] } ] + 'function-nesting': + 'begin': '\\(' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.function.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.function.end.bracket.round.css' + 'patterns': [ + { + 'include': '#arithmetic-operators' + } + { + 'include': '#property-values' + }, + { + 'include': '#function-nesting' + } + ] 'functional-pseudo-classes': 'patterns': [ { From 8f3650dd4b8b4bde3a9b06fd98a9976eb27be015 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:42:47 +0700 Subject: [PATCH 20/31] Include `#rule-list-innards` in at-rules --- src/vscode-css/grammars/css.cson | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 506a89c..d26e7d1 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -217,6 +217,9 @@ 'name': 'punctuation.section.media.end.bracket.curly.css' 'name': 'meta.at-rule.media.body.css' 'patterns': [ + { + 'include': '#rule-list-innards' + } { 'include': '$self' } @@ -635,6 +638,9 @@ 'name': 'punctuation.section.end.bracket.curly.css' 'name': 'meta.at-rule.body.css' 'patterns': [ + { + 'include': '#rule-list-innards' + } { 'include': '$self' } From b678b3a5d458802cbe3392e482e83c9ecaa5d384 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:46:40 +0700 Subject: [PATCH 21/31] Add `&` to list of valid selector character --- src/vscode-css/grammars/css.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index d26e7d1..28b6755 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1785,7 +1785,7 @@ (?= (?:\\|)? # Possible anonymous namespace prefix (?: - [-\\[:.*\\#a-zA-Z_] # Valid selector character + [-\\[:.*&\\#a-zA-Z_] # Valid selector character | [^\\x00-\\x7F] # Which can include non-ASCII symbols | From c359822c0874c4b18074cf9fd0d0ab0192be4001 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 10:48:51 +0700 Subject: [PATCH 22/31] Replace spelling of "colour" with "color" for naming consistency --- src/vscode-css/grammars/css.cson | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 28b6755..b28e9b5 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -652,12 +652,12 @@ 'color-keywords': 'patterns': [ { - # CSS 2.1 colours: http://www.w3.org/TR/CSS21/syndata.html#value-def-color + # CSS 2.1 colors: http://www.w3.org/TR/CSS21/syndata.html#value-def-color 'match': '(?i)(? Date: Sun, 1 Jun 2025 10:52:27 +0700 Subject: [PATCH 23/31] Include `#functions` in `media-query` for functions such as `calc()` --- src/vscode-css/grammars/css.cson | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index b28e9b5..a8ceb11 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1386,6 +1386,9 @@ { 'include': '#comment-block' } + { + 'include': '#functions' + } ] } ] From 9915121e831338e028496e60f41b5eac03144537 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 11:02:50 +0700 Subject: [PATCH 24/31] Update `#selector` rules and include it in the `rule-list-innards` patterns --- src/vscode-css/grammars/css.cson | 76 +++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index a8ceb11..3fdf0ce 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1749,6 +1749,9 @@ ''' 'name': 'variable.css' } + { + 'include': '#selector' + } { 'begin': '(?~+] # 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: (?= - (?:\\|)? # Possible anonymous namespace prefix + \\s* # Optional whitespace and one of the following: (?: - [-\\[:.*&\\#a-zA-Z_] # Valid selector character - | - [^\\x00-\\x7F] # Which can include non-ASCII symbols - | - \\\\ # Or an escape sequence - (?:[0-9a-fA-F]{1,6}|.) + \\/ # Comment + | @ # At-rule + | { # Opening property list brace + | \\) # Closing function brace (for passing test on `some-edgy-new-function(`) + | $ # End of line ) ) - ''' - 'end': '(?=\\s*[/@{)])' + ''' 'name': 'meta.selector.css' 'patterns': [ { From 27a573a6e9e61cc05cf92cfeb42cc9238696f0b9 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 11:05:22 +0700 Subject: [PATCH 25/31] Add `#combinator-invalid` scope to work with new `#selector` rules --- src/vscode-css/grammars/css.cson | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 3fdf0ce..c27f1e7 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -23,7 +23,7 @@ 'include': '#escapes' } { - 'include': '#combinators' + 'include': '#combinator-invalid' } { 'include': '#selector' @@ -698,14 +698,16 @@ 'combinators': 'patterns': [ { - 'match': '/deep/|>>>' - 'name': 'invalid.deprecated.combinator.css' + 'include': '#combinator-invalid' } { 'match': '>>|>|\\+|~' 'name': 'keyword.operator.combinator.css' } ] + 'combinator-invalid': + 'match': '/deep/|>>>' + 'name': 'invalid.deprecated.combinator.css' 'commas': 'match': ',' 'name': 'punctuation.separator.list.comma.css' From 942299bf9eb28f40a3937aaab1cb492d3b854cd2 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 11:12:16 +0700 Subject: [PATCH 26/31] Update scopes for property-names --- src/vscode-css/grammars/css.cson | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index c27f1e7..b11e30a 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1621,11 +1621,11 @@ ) (?![\\w-]) ''' - 'name': 'support.type.property-name.css' + 'name': 'meta.property-name.css support.type.property-name.css' } { 'match': '(? Date: Sun, 1 Jun 2025 11:23:36 +0700 Subject: [PATCH 27/31] Add `#shared-names` for selector and property names --- src/vscode-css/grammars/css.cson | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index b11e30a..8e06d41 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -1751,6 +1751,9 @@ ''' 'name': 'variable.css' } + { + 'include': '#shared-names' + } { 'include': '#property-names' } @@ -2112,6 +2115,135 @@ 'name': 'entity.name.tag.custom.css' } ] + 'shared-names': + # Shared names are keywords that are listed in both selector and property name contexts. + 'patterns': [ + # The following are considered selectors by default: + { + 'begin': '''(?xi) + # Selector match must be preceded by one of the following: + (?<= + ^ # Start of line + | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon + | [{}] # Opening or closing brace (condensed property list syntax) + | \\*/ # Comment end + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + ) + + (?= + # Selector must match: + (?: + # HTML elements + header|image|label|marquee|mask|nav|ruby|shadow|span|style + # SVG elements + |color-profile|line|text + ) + + # Selector must NOT be followed by any any of the following: + (?! + .*; # Any characters followed by a semicolon (denotes a property) + | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property) + | - # A dash (denotes a property name) + | \\:\\s+ # A colon followed by whitespace (denotes a property name) + ) + ) + ''' + 'end': '''(?xi) + # Selector match ends with one of the following: + (?= + \\s # Whitespace + | \\/\\* # Comment + | , # Comma + | { # Opening property list brace + | $ # End of line + ) + ''' + 'patterns': [ + { + 'include': '#selector' + } + ] + } + # The following are considered property names by default when attached to a colon: + { + 'begin': '''(?xi) + # Selector match must be preceded by one of the following: + (?<= + ^ # Start of line + | (^|[^\\:])\\s # Whitespace, after the start of a line or any character except a colon + | [{}] # Opening or closing brace (condensed property list syntax) + | \\*/ # Comment end + | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence + ) + + (?= + # Selector must match: + (?: + # HTML elements + content|font|mark + # SVG elements + |cursor|filter + ) + + # Selector must NOT be followed by any any of the following: + (?! + .*; # Any characters followed by a semicolon (denotes a property) + | [^{]*} # Any characters, except an opening brace, followed by a closing bracket (denotes a property) + | - # A dash (denotes a property name) + | \\: # A colon, unless it's with one of the following: + (?! + # A opening bracket before an closing bracket + [^}]*{ + # A pseudo-class selectors + | active|any-link|checked|disabled|empty|enabled|first + | (?:first|last|only)-(?:child|of-type)|focus|focus-visible|focus-within|fullscreen|host|hover + | in-range|indeterminate|invalid|link|out-of-range + | read-only|read-write|required|root|scope|target|unresolved + | valid|visited + + # A functional pseudo-class selectors + | (?: dir|lang + | not|has|matches|where|is + | nth-(?:last-)?(?:child|of-type) + )\\( + + # A single-colon pseudo-element selectors + | 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-]+ + ) + ) + ) + ''' + 'end': '''(?xi) + # Selector match ends with one of the following: + (?= + \\s # Whitespace + | \\/\\* # Comment + | , # Comma + | { # Opening property list brace + | $ # End of line + ) + ''' + 'patterns': [ + { + 'include': '#selector' + } + ] + } + ] 'string': 'patterns': [ { From 325e0ea154c756562343ae89038420b87f0366a5 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 11:28:29 +0700 Subject: [PATCH 28/31] Update demos --- .dev-assets/syntax-issues/bugs/property-value-slash-issue.css | 3 +++ demo/shared-names-demo.css | 2 +- demo/shared-values-demo.css | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css b/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css index 9430638..12e5bfb 100644 --- a/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css +++ b/.dev-assets/syntax-issues/bugs/property-value-slash-issue.css @@ -6,4 +6,7 @@ .example { 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/demo/shared-names-demo.css b/demo/shared-names-demo.css index 9a66b05..3e3614d 100644 --- a/demo/shared-names-demo.css +++ b/demo/shared-names-demo.css @@ -52,7 +52,7 @@ } span { - span:initial + span: initial } style { diff --git a/demo/shared-values-demo.css b/demo/shared-values-demo.css index 89e0955..405829a 100644 --- a/demo/shared-values-demo.css +++ b/demo/shared-values-demo.css @@ -5,7 +5,7 @@ select :content, select:content, -select:not(something):content, +select:not(something):content { color:content } From d9745d95d8179fab5c1f6abc6883475f4051cd39 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Sun, 1 Jun 2025 12:36:58 +0700 Subject: [PATCH 29/31] Add `light-dark` to color functions --- src/vscode-css/grammars/css.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index 8e06d41..cea01b3 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -841,7 +841,7 @@ } # Colors { - 'begin': '(?i)(? Date: Sun, 1 Jun 2025 12:45:45 +0700 Subject: [PATCH 30/31] Convert cson to json file --- syntaxes/css.tmLanguage.json | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index ff8d183..b03978e 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -7,9 +7,6 @@ ], "firstLineMatch": "(?xi)\n# Emacs modeline\n-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n css\n(?=[\\s;]|(?]?\\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": "#commas" - }, { "include": "#comment-block" }, @@ -509,9 +506,6 @@ }, "name": "meta.at-rule.supports.body.css", "patterns": [ - { - "include": "#rule-list-innards" - }, { "include": "$self" } @@ -870,7 +864,7 @@ ] }, { - "begin": "(?i)(? Date: Thu, 5 Jun 2025 15:13:58 +0700 Subject: [PATCH 31/31] Remove `light-dark` to match `css.cson` in official PR --- src/vscode-css/grammars/css.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-css/grammars/css.cson b/src/vscode-css/grammars/css.cson index cea01b3..8e06d41 100644 --- a/src/vscode-css/grammars/css.cson +++ b/src/vscode-css/grammars/css.cson @@ -841,7 +841,7 @@ } # Colors { - 'begin': '(?i)(?