├── .github ├── no-response.yml └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── coffeelint.json ├── grammars └── less.cson ├── package.json ├── settings └── language-less.cson ├── spec └── less-spec.coffee └── update.coffee /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 28 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | Test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | channel: [stable, beta] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: UziTech/action-setup-atom@v2 18 | with: 19 | version: ${{ matrix.channel }} 20 | - name: Install dependencies 21 | run: apm install 22 | - name: Run tests 23 | run: atom --test spec 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md) 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------- 23 | 24 | This package was derived from a TextMate bundle located at 25 | https://github.com/textmate/less.tmbundle and distributed under the following 26 | license, located in `LICENSE.md`: 27 | 28 | Copyright (c) 2010 Scott Kyle and Rasmus Andersson 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining a copy 31 | of this software and associated documentation files (the "Software"), to deal 32 | in the Software without restriction, including without limitation the rights 33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | copies of the Software, and to permit persons to whom the Software is 35 | furnished to do so, subject to the following conditions: 36 | 37 | The above copyright notice and this permission notice shall be included in 38 | all copies or substantial portions of the Software. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 45 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 46 | THE SOFTWARE. 47 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # Less language support in Atom 3 | [![macOS Build Status](https://travis-ci.org/atom/language-less.svg?branch=master)](https://travis-ci.org/atom/language-less) 4 | [![Windows Build Sstatus](https://ci.appveyor.com/api/projects/status/aeina4fr4b0i7yay/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/language-less/branch/master) 5 | [![Dependency Status](https://david-dm.org/atom/language-less.svg)](https://david-dm.org/atom/language-less) 6 | 7 | Adds syntax highlighting to [Less](http://lesscss.org) files in Atom. 8 | 9 | Originally [converted](http://flight-manual.atom.io/hacking-atom/sections/converting-from-textmate) from the [Less TextMate bundle](https://github.com/textmate/less.tmbundle). 10 | 11 | Contributions are greatly appreciated. Please fork this repository and open a pull request to add snippets, make grammar tweaks, etc. 12 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_line_length": { 3 | "level": "ignore" 4 | }, 5 | "no_empty_param_list": { 6 | "level": "error" 7 | }, 8 | "arrow_spacing": { 9 | "level": "error" 10 | }, 11 | "no_interpolation_in_single_quotes": { 12 | "level": "error" 13 | }, 14 | "no_debugger": { 15 | "level": "error" 16 | }, 17 | "prefer_english_operator": { 18 | "level": "error" 19 | }, 20 | "colon_assignment_spacing": { 21 | "spacing": { 22 | "left": 0, 23 | "right": 1 24 | }, 25 | "level": "error" 26 | }, 27 | "braces_spacing": { 28 | "spaces": 0, 29 | "level": "error" 30 | }, 31 | "spacing_after_comma": { 32 | "level": "error" 33 | }, 34 | "no_stand_alone_at": { 35 | "level": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /grammars/less.cson: -------------------------------------------------------------------------------- 1 | 'name': 'Less' 2 | 'scopeName': 'source.css.less' 3 | 'fileTypes': [ 4 | 'less' 5 | 'less.erb' 6 | ] 7 | 'patterns': [ 8 | { 9 | 'include': '#strings' 10 | } 11 | { 12 | 'captures': 13 | '1': 14 | 'name': 'entity.other.attribute-name.class.mixin.css' 15 | 'match': '(\\.[_a-zA-Z][a-zA-Z0-9_-]*(?=\\())' 16 | } 17 | { 18 | 'captures': 19 | '1': 20 | 'name': 'entity.other.attribute-name.class.css' 21 | '2': 22 | 'name': 'punctuation.definition.entity.css' 23 | '4': 24 | 'name': 'variable.other.interpolation.less' 25 | 'match': '((\\.)([_a-zA-Z]|(@{[a-zA-Z0-9_-]+}))[a-zA-Z0-9_-]*)' 26 | } 27 | { 28 | 'captures': 29 | '0': 30 | 'name': 'entity.other.attribute-name.parent-selector.css' 31 | '1': 32 | 'name': 'punctuation.definition.entity.css' 33 | 'match': '(&)[a-zA-Z0-9_-]*' 34 | } 35 | { 36 | 'begin': '(format|local|url|attr|counter|counters)\\s*(\\()' 37 | 'beginCaptures': 38 | '1': 39 | 'name': 'support.function.misc.css' 40 | '2': 41 | 'name': 'punctuation.section.function.css' 42 | 'end': '\\)' 43 | 'endCaptures': 44 | '0': 45 | 'name': 'punctuation.section.function.css' 46 | 'patterns': [ 47 | { 48 | 'begin': '\'' 49 | 'beginCaptures': 50 | '0': 51 | 'name': 'punctuation.definition.string.begin.css' 52 | 'end': '\'' 53 | 'endCaptures': 54 | '0': 55 | 'name': 'punctuation.definition.string.end.css' 56 | 'name': 'string.quoted.single.css' 57 | 'patterns': [ 58 | { 59 | 'match': '\\\\.' 60 | 'name': 'constant.character.escape.css' 61 | } 62 | ] 63 | } 64 | { 65 | 'begin': '"' 66 | 'beginCaptures': 67 | '0': 68 | 'name': 'punctuation.definition.string.begin.css' 69 | 'end': '"' 70 | 'endCaptures': 71 | '0': 72 | 'name': 'punctuation.definition.string.end.css' 73 | 'name': 'string.quoted.double.css' 74 | 'patterns': [ 75 | { 76 | 'match': '\\\\(\\d{1,6}|.)' 77 | 'name': 'constant.character.escape.css' 78 | } 79 | ] 80 | } 81 | { 82 | 'match': '[^\'") \\t]+' 83 | 'name': 'variable.parameter.misc.css' 84 | } 85 | ] 86 | } 87 | { 88 | 'match': '(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\\b(?!.*?(?([\'"])(?:[^\\\\]|\\\\.)*?(\\6)))))?\\s*(\\])' 135 | 'name': 'meta.attribute-selector.css' 136 | } 137 | { 138 | 'begin': '((@)import\\b)' 139 | 'beginCaptures': 140 | '1': 141 | 'name': 'keyword.control.at-rule.import.less' 142 | '2': 143 | 'name': 'punctuation.definition.keyword.less' 144 | 'end': ';' 145 | 'endCaptures': 146 | '0': 147 | 'name': 'punctuation.terminator.rule.css' 148 | 'name': 'meta.at-rule.import.css' 149 | 'patterns': [ 150 | { 151 | 'match': '(?<=\\(|,|\\s)\\b(reference|optional|once|multiple|less|inline)\\b(?=\\)|,)' 152 | 'name': 'keyword.control.import.option.less' 153 | } 154 | { 155 | 'include': '#brace_round' 156 | } 157 | { 158 | 'include': 'source.css#commas' 159 | } 160 | { 161 | 'include': '#strings' 162 | } 163 | ] 164 | } 165 | { 166 | 'captures': 167 | '1': 168 | 'name': 'keyword.control.at-rule.fontface.css' 169 | '2': 170 | 'name': 'punctuation.definition.keyword.css' 171 | 'match': '^\\s*((@)font-face\\b)' 172 | 'name': 'meta.at-rule.fontface.css' 173 | } 174 | { 175 | 'captures': 176 | '1': 177 | 'name': 'keyword.control.at-rule.media.css' 178 | '2': 179 | 'name': 'punctuation.definition.keyword.css' 180 | 'match': '^\\s*((@)media\\b)' 181 | 'name': 'meta.at-rule.media.css' 182 | } 183 | { 184 | 'include': 'source.css#media-features' 185 | } 186 | { 187 | 'match': '\\b(tv|tty|screen|projection|print|handheld|embossed|braille|aural|all)\\b' 188 | 'name': 'support.constant.media-type.media.css' 189 | } 190 | { 191 | 'match': '\\b(portrait|landscape)\\b' 192 | 'name': 'support.constant.property-value.media-property.media.css' 193 | } 194 | { 195 | 'captures': 196 | '1': 197 | 'name': 'support.function.less' 198 | 'match': '(\\.[a-zA-Z0-9_-]+)\\s*(;|\\()' 199 | } 200 | { 201 | 'begin': '(^[ \\t]+)?(?=//)' 202 | 'beginCaptures': 203 | '1': 204 | 'name': 'punctuation.whitespace.comment.leading.less' 205 | 'end': '(?!\\G)' 206 | 'patterns': [ 207 | { 208 | 'begin': '//' 209 | 'beginCaptures': 210 | '0': 211 | 'name': 'punctuation.definition.comment.less' 212 | 'end': '\\n' 213 | 'name': 'comment.line.double-slash.less' 214 | } 215 | ] 216 | } 217 | { 218 | 'match': '(@|\\-\\-)[\\w-]+(?=\\s*)' 219 | 'name': 'variable.other.less' 220 | 'captures': 221 | '1': 222 | 'name': 'punctuation.definition.variable.less' 223 | } 224 | { 225 | 'include': '#variable_interpolation' 226 | } 227 | ( 228 | 'begin': '{' 229 | 'beginCaptures': 230 | '0': 231 | 'name': 'punctuation.section.property-list.begin.bracket.curly.css' 232 | 'end': '}' 233 | 'endCaptures': 234 | '0': 235 | 'name': 'punctuation.section.property-list.end.bracket.curly.css' 236 | 'name': 'meta.property-list.css' 237 | 'patterns': [ 238 | { 239 | 'include': 'source.css#pseudo-elements' 240 | } 241 | { 242 | 'include': 'source.css#pseudo-classes' 243 | } 244 | { 245 | 'include': 'source.css#tag-names' 246 | } 247 | { 248 | 'include': 'source.css#commas' 249 | } 250 | { 251 | 'include': '#variable_interpolation' 252 | } 253 | { 254 | 'include': 'source.css#property-names' 255 | } 256 | { 257 | 'include': '#property_values' 258 | } 259 | { 260 | 'include': '$self' 261 | } 262 | ] 263 | ) 264 | { 265 | "match": "\\!\\s*important" 266 | "name": "keyword.other.important.css" 267 | } 268 | { 269 | 'match': '\\*|\\/|\\-|\\+|~|=|<=|>=|<|>' 270 | 'name': 'keyword.operator.less' 271 | } 272 | { 273 | 'match': '\\b(not|and|when)\\b' 274 | 'name': 'keyword.control.logical.operator.less' 275 | } 276 | { 277 | 'include': 'source.css#tag-names' 278 | } 279 | { 280 | 'match': '(? 50%" 149 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-percentage" 150 | } 151 | { 152 | type: "function" 153 | rightLabel: "Less Builtin" 154 | snippet: "round(${1:number}, ${2:[places: 0]})${3:;}$0" 155 | description: "rounds a number to a number of places" 156 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-round" 157 | } 158 | { 159 | type: "function" 160 | rightLabel: "Less Builtin" 161 | snippet: "sqrt(${1:number})${2:;}$0" 162 | description: "calculates square root of a number" 163 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-sqrt" 164 | } 165 | { 166 | type: "function" 167 | rightLabel: "Less Builtin" 168 | snippet: "sin(${1:number})${2:;}$0" 169 | description: "sine function" 170 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-sin" 171 | } 172 | { 173 | type: "function" 174 | rightLabel: "Less Builtin" 175 | snippet: "tan(${1:number})${2:;}$0" 176 | description: "tangent function" 177 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-tan" 178 | } 179 | { 180 | type: "function" 181 | rightLabel: "Less Builtin" 182 | snippet: "atan(${1:number})${2:;}$0" 183 | description: "arctangent - inverse of tangent function" 184 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-atan" 185 | } 186 | { 187 | type: "function" 188 | rightLabel: "Less Builtin" 189 | snippet: "pi()$0" 190 | description: "returns pi" 191 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-pi" 192 | } 193 | { 194 | type: "function" 195 | rightLabel: "Less Builtin" 196 | snippet: "pow(${1:@base}, ${2:@exponent})${3:;}$0" 197 | description: "first argument raised to the power of the second argument" 198 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-pow" 199 | } 200 | { 201 | type: "function" 202 | rightLabel: "Less Builtin" 203 | snippet: "mod(${1:number}, ${2:number})${3:;}$0" 204 | description: "first argument modulus second argument" 205 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-mod" 206 | } 207 | { 208 | type: "function" 209 | rightLabel: "Less Builtin" 210 | snippet: "min(${1:@x}, ${2:@y})${3:;}$0" 211 | description: "returns the lowest of one or more values" 212 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-min" 213 | } 214 | { 215 | type: "function" 216 | rightLabel: "Less Builtin" 217 | snippet: "max(${1:@x}, ${2:@y})${3:;}$0" 218 | description: "returns the lowest of one or more values" 219 | descriptionMoreURL: "http://lesscss.org/functions/#math-functions-max" 220 | } 221 | { 222 | type: "function" 223 | rightLabel: "Less Builtin" 224 | snippet: "rgb(${1:@r}, ${2:@g}, ${3:@b})${4:;}$0" 225 | description: "converts to a color" 226 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-rgb" 227 | } 228 | { 229 | type: "function" 230 | rightLabel: "Less Builtin" 231 | snippet: "rgba(${1:@r}, ${2:@g}, ${3:@b}, ${4:@a})${5:;}$0" 232 | description: "converts to a color" 233 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-rgba" 234 | } 235 | { 236 | type: "function" 237 | rightLabel: "Less Builtin" 238 | snippet: "argb(${1:@color})${2:;}$0" 239 | description: "creates a #AARRGGBB" 240 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-argb" 241 | } 242 | { 243 | type: "function" 244 | rightLabel: "Less Builtin" 245 | snippet: "hsl(${1:@hue}, ${2:@saturation}, ${3:@lightness})${4:;}$0" 246 | description: "creates a color" 247 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-hsl" 248 | } 249 | { 250 | type: "function" 251 | rightLabel: "Less Builtin" 252 | snippet: "hsla(${1:@hue}, ${2:@saturation}, ${3:@lightness}, ${4:@alpha})${5:;}$0" 253 | description: "creates a color" 254 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-hsla" 255 | } 256 | { 257 | type: "function" 258 | rightLabel: "Less Builtin" 259 | snippet: "hsv(${1:@hue}, ${2:@saturation}, ${3:@value})${4:;}$0" 260 | description: "creates a color" 261 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-hsv" 262 | } 263 | { 264 | type: "function" 265 | rightLabel: "Less Builtin" 266 | snippet: "hsva(${1:@hue}, ${2:@saturation}, ${3:@value}, ${4:@alpha})${5:;}$0" 267 | description: "creates a color" 268 | descriptionMoreURL: "http://lesscss.org/functions/#color-definition-hsva" 269 | } 270 | { 271 | type: "function" 272 | rightLabel: "Less Builtin" 273 | snippet: "hue(${1:@color})${2:;}$0" 274 | description: "returns the `hue` channel of `@color` in the HSL space" 275 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-hue" 276 | } 277 | { 278 | type: "function" 279 | rightLabel: "Less Builtin" 280 | snippet: "saturation(${1:@color})${2:;}$0" 281 | description: "returns the `saturation` channel of `@color` in the HSL space" 282 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-saturation" 283 | } 284 | { 285 | type: "function" 286 | rightLabel: "Less Builtin" 287 | snippet: "lightness(${1:@color})${2:;}$0" 288 | description: "returns the `lightness` channel of `@color` in the HSL space" 289 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-lightness" 290 | } 291 | { 292 | type: "function" 293 | rightLabel: "Less Builtin" 294 | snippet: "hsvhue(${1:@color})${2:;}$0" 295 | description: "returns the `hue` channel of `@color` in the HSV space" 296 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-hsvhue" 297 | } 298 | { 299 | type: "function" 300 | rightLabel: "Less Builtin" 301 | snippet: "hsvsaturation(${1:@color})${2:;}$0" 302 | description: "returns the `saturation` channel of `@color` in the HSV space" 303 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-hsvsaturation" 304 | } 305 | { 306 | type: "function" 307 | rightLabel: "Less Builtin" 308 | snippet: "hsvvalue(${1:@color})${2:;}$0" 309 | description: "returns the `value` channel of `@color` in the HSV space" 310 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-hsvvalue" 311 | } 312 | { 313 | type: "function" 314 | rightLabel: "Less Builtin" 315 | snippet: "red(${1:@color})${2:;}$0" 316 | description: "returns the `red` channel of `@color`" 317 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-red" 318 | } 319 | { 320 | type: "function" 321 | rightLabel: "Less Builtin" 322 | snippet: "green(${1:@color})${2:;}$0" 323 | description: "returns the `green` channel of `@color`" 324 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-green" 325 | } 326 | { 327 | type: "function" 328 | rightLabel: "Less Builtin" 329 | snippet: "blue(${1:@color})${2:;}$0" 330 | description: "returns the `blue` channel of `@color`" 331 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-blue" 332 | } 333 | { 334 | type: "function" 335 | rightLabel: "Less Builtin" 336 | snippet: "alpha(${1:@color})${2:;}$0" 337 | description: "returns the `alpha` channel of `@color`" 338 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-alpha" 339 | } 340 | { 341 | type: "function" 342 | rightLabel: "Less Builtin" 343 | snippet: "luma(${1:@color})${2:;}$0" 344 | description: "returns the `luma` value (perceptual brightness) of `@color`" 345 | descriptionMoreURL: "http://lesscss.org/functions/#color-channel-luma" 346 | } 347 | { 348 | type: "function" 349 | rightLabel: "Less Builtin" 350 | snippet: "saturate(${1:@color}, ${2:10%})${3:;}$0" 351 | description: "return `@color` 10% points _more_ saturated" 352 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-saturate" 353 | } 354 | { 355 | type: "function" 356 | rightLabel: "Less Builtin" 357 | snippet: "desaturate(${1:@color}, ${2:10%})${3:;}$0" 358 | description: "return `@color` 10% points _less_ saturated" 359 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-desaturate" 360 | } 361 | { 362 | type: "function" 363 | rightLabel: "Less Builtin" 364 | snippet: "lighten(${1:@color}, ${2:10%})${3:;}$0" 365 | description: "return `@color` 10% points _lighter_" 366 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-lighten" 367 | } 368 | { 369 | type: "function" 370 | rightLabel: "Less Builtin" 371 | snippet: "darken(${1:@color}, ${2:10%})${3:;}$0" 372 | description: "return `@color` 10% points _darker_" 373 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-darken" 374 | } 375 | { 376 | type: "function" 377 | rightLabel: "Less Builtin" 378 | snippet: "fadein(${1:@color}, ${2:10%})${3:;}$0" 379 | description: "return `@color` 10% points _less_ transparent" 380 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-fadein" 381 | } 382 | { 383 | type: "function" 384 | rightLabel: "Less Builtin" 385 | snippet: "fadeout(${1:@color}, ${2:10%})${3:;}$0" 386 | description: "return `@color` 10% points _more_ transparent" 387 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-fadeout" 388 | } 389 | { 390 | type: "function" 391 | rightLabel: "Less Builtin" 392 | snippet: "fade(${1:@color}, ${2:50%})${3:;}$0" 393 | description: "return `@color` with 50% transparency" 394 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-fade" 395 | } 396 | { 397 | type: "function" 398 | rightLabel: "Less Builtin" 399 | snippet: "spin(${1:@color}, ${2:10})${3:;}$0" 400 | description: "return `@color` with a 10 degree larger in hue" 401 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-spin" 402 | } 403 | { 404 | type: "function" 405 | rightLabel: "Less Builtin" 406 | snippet: "mix(${1:@color1}, ${2:@color2}, ${3:[@weight: 50%]})${4:;}$0" 407 | description: "return a mix of `@color1` and `@color2`" 408 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-mix" 409 | } 410 | { 411 | type: "function" 412 | rightLabel: "Less Builtin" 413 | snippet: "greyscale(${1:@color})${2:;}$0" 414 | description: "returns a grey, 100% desaturated color" 415 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-greyscale" 416 | } 417 | { 418 | type: "function" 419 | rightLabel: "Less Builtin" 420 | snippet: "contrast(${1:@color1}, ${2:[@darkcolor: black]}, ${3:[@lightcolor: white]}, ${4:[@threshold: 43%]})${5:;}$0" 421 | description: "return `@darkcolor` if `@color1 is> 43% luma` otherwise return `@lightcolor`, see notes" 422 | descriptionMoreURL: "http://lesscss.org/functions/#color-operations-contrast" 423 | } 424 | { 425 | type: "function" 426 | rightLabel: "Less Builtin" 427 | snippet: "multiply(${1:@color1}, ${2:@color2})${3:;}$0" 428 | description: "description..." 429 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-multiply" 430 | } 431 | { 432 | type: "function" 433 | rightLabel: "Less Builtin" 434 | snippet: "screen(${1:@color1}, ${2:@color2})${3:;}$0" 435 | description: "description..." 436 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-screen" 437 | } 438 | { 439 | type: "function" 440 | rightLabel: "Less Builtin" 441 | snippet: "overlay(${1:@color1}, ${2:@color2})${3:;}$0" 442 | description: "description..." 443 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-overlay" 444 | } 445 | { 446 | type: "function" 447 | rightLabel: "Less Builtin" 448 | snippet: "softlight(${1:@color1}, ${2:@color2})${3:;}$0" 449 | description: "description..." 450 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-softlight" 451 | } 452 | { 453 | type: "function" 454 | rightLabel: "Less Builtin" 455 | snippet: "hardlight(${1:@color1}, ${2:@color2})${3:;}$0" 456 | description: "description..." 457 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-hardlight" 458 | } 459 | { 460 | type: "function" 461 | rightLabel: "Less Builtin" 462 | snippet: "difference(${1:@color1}, ${2:@color2})${3:;}$0" 463 | description: "description..." 464 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-difference" 465 | } 466 | { 467 | type: "function" 468 | rightLabel: "Less Builtin" 469 | snippet: "exclusion(${1:@color1}, ${2:@color2})${3:;}$0" 470 | description: "description..." 471 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-exclusion" 472 | } 473 | { 474 | type: "function" 475 | rightLabel: "Less Builtin" 476 | snippet: "average(${1:@color1}, ${2:@color2})${3:;}$0" 477 | description: "description..." 478 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-average" 479 | } 480 | { 481 | type: "function" 482 | rightLabel: "Less Builtin" 483 | snippet: "negation(${1:@color1}, ${2:@color2})${3:;}$0" 484 | description: "description..." 485 | descriptionMoreURL: "http://lesscss.org/functions/#color-blending-negation" 486 | } 487 | ] 488 | -------------------------------------------------------------------------------- /spec/less-spec.coffee: -------------------------------------------------------------------------------- 1 | describe "Less grammar", -> 2 | grammar = null 3 | 4 | beforeEach -> 5 | waitsForPromise -> 6 | atom.packages.activatePackage("language-css") 7 | 8 | waitsForPromise -> 9 | atom.packages.activatePackage("language-less") 10 | 11 | runs -> 12 | grammar = atom.grammars.grammarForScopeName("source.css.less") 13 | 14 | it "parses the grammar", -> 15 | expect(grammar).toBeDefined() 16 | expect(grammar.scopeName).toBe "source.css.less" 17 | 18 | it "parses numbers", -> 19 | {tokens} = grammar.tokenizeLine(" 10") 20 | expect(tokens).toHaveLength 2 21 | expect(tokens[0]).toEqual value: " ", scopes: ['source.css.less'] 22 | expect(tokens[1]).toEqual value: "10", scopes: ['source.css.less', 'constant.numeric.css'] 23 | 24 | {tokens} = grammar.tokenizeLine("-.1") 25 | expect(tokens).toHaveLength 1 26 | expect(tokens[0]).toEqual value: "-.1", scopes: ['source.css.less', 'constant.numeric.css'] 27 | 28 | {tokens} = grammar.tokenizeLine(".4") 29 | expect(tokens).toHaveLength 1 30 | expect(tokens[0]).toEqual value: ".4", scopes: ['source.css.less', 'constant.numeric.css'] 31 | 32 | it 'parses color names', -> 33 | {tokens} = grammar.tokenizeLine '.foo { color: rebeccapurple; background: whitesmoke; }' 34 | expect(tokens[8]).toEqual value: "rebeccapurple", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-extended-color-name.css'] 35 | expect(tokens[14]).toEqual value: "whitesmoke", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-extended-color-name.css'] 36 | 37 | it "parses property names", -> 38 | {tokens} = grammar.tokenizeLine("{display: none;}") 39 | expect(tokens[1]).toEqual value: "display", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 40 | 41 | {tokens} = grammar.tokenizeLine("{displaya: none;}") 42 | expect(tokens[1]).toEqual value: "displaya", scopes: ['source.css.less', 'meta.property-list.css'] 43 | 44 | it "parses property names distinctly from property values with the same text", -> 45 | {tokens} = grammar.tokenizeLine("{left: left;}") 46 | expect(tokens).toHaveLength 7 47 | expect(tokens[1]).toEqual value: "left", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 48 | expect(tokens[2]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 49 | expect(tokens[3]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 50 | expect(tokens[4]).toEqual value: "left", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 51 | expect(tokens[5]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 52 | 53 | {tokens} = grammar.tokenizeLine("{left:left;}") 54 | expect(tokens).toHaveLength 6 55 | expect(tokens[1]).toEqual value: "left", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 56 | expect(tokens[2]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 57 | expect(tokens[3]).toEqual value: "left", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 58 | expect(tokens[4]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 59 | 60 | it "parses property names distinctly from element selectors with the same prefix", -> 61 | {tokens} = grammar.tokenizeLine("{table-layout: fixed;}") 62 | expect(tokens).toHaveLength 7 63 | expect(tokens[1]).toEqual value: "table-layout", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 64 | expect(tokens[2]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 65 | expect(tokens[3]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 66 | expect(tokens[4]).toEqual value: "fixed", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 67 | expect(tokens[5]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 68 | 69 | it "does not parse @media conditions as a property-list", -> 70 | {tokens} = grammar.tokenizeLine('@media (min-resolution: 2dppx) {}') 71 | expect(tokens[4].scopes).not.toContain 'support.type.property-name.css' 72 | expect(tokens[7].scopes).not.toContain 'meta.property-value.css' 73 | expect(tokens[11].scopes).not.toContain 'meta.property-value.css' 74 | 75 | it "parses @media features", -> 76 | {tokens} = grammar.tokenizeLine('@media (min-width: 100px) {}') 77 | expect(tokens[0]).toEqual value: "@", scopes: ['source.css.less', 'meta.at-rule.media.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'] 78 | expect(tokens[1]).toEqual value: "media", scopes: ['source.css.less', 'meta.at-rule.media.css', 'keyword.control.at-rule.media.css'] 79 | expect(tokens[4]).toEqual value: "min-width", scopes: ['source.css.less', 'support.type.property-name.media.css'] 80 | expect(tokens[7]).toEqual value: "100", scopes: ['source.css.less', 'constant.numeric.css'] 81 | expect(tokens[8]).toEqual value: "px", scopes: ['source.css.less', 'constant.numeric.css', 'keyword.other.unit.px.css'] 82 | 83 | it "parses @media orientation", -> 84 | {tokens} = grammar.tokenizeLine('@media (orientation: portrait){}') 85 | expect(tokens[4]).toEqual value: "orientation", scopes: ['source.css.less', 'support.type.property-name.media.css'] 86 | expect(tokens[7]).toEqual value: "portrait", scopes: ['source.css.less', 'support.constant.property-value.media-property.media.css'] 87 | 88 | it "parses parent selector", -> 89 | {tokens} = grammar.tokenizeLine('& .foo {}') 90 | expect(tokens[0]).toEqual value: "&", scopes: ['source.css.less', 'entity.other.attribute-name.parent-selector.css', 'punctuation.definition.entity.css'] 91 | expect(tokens[1]).toEqual value: " ", scopes: ['source.css.less'] 92 | expect(tokens[2]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 93 | expect(tokens[3]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 94 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less'] 95 | expect(tokens[5]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 96 | expect(tokens[6]).toEqual value: "}", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 97 | 98 | {tokens} = grammar.tokenizeLine('&:hover {}') 99 | expect(tokens[0]).toEqual value: "&", scopes: ['source.css.less', 'entity.other.attribute-name.parent-selector.css', 'punctuation.definition.entity.css'] 100 | expect(tokens[1]).toEqual value: ":", scopes: ['source.css.less', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'] 101 | expect(tokens[2]).toEqual value: "hover", scopes: ['source.css.less', 'entity.other.attribute-name.pseudo-class.css'] 102 | expect(tokens[3]).toEqual value: " ", scopes: ['source.css.less'] 103 | expect(tokens[4]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 104 | expect(tokens[5]).toEqual value: "}", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 105 | 106 | it "parses pseudo element", -> 107 | {tokens} = grammar.tokenizeLine('.foo::after {}') 108 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 109 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 110 | expect(tokens[2]).toEqual value: "::", scopes: ['source.css.less', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'] 111 | expect(tokens[3]).toEqual value: "after", scopes: ['source.css.less', 'entity.other.attribute-name.pseudo-element.css'] 112 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less'] 113 | expect(tokens[5]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 114 | expect(tokens[6]).toEqual value: "}", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 115 | 116 | it "parses id selectors", -> 117 | {tokens} = grammar.tokenizeLine("#abc {}") 118 | expect(tokens[0]).toEqual value: "#", scopes: ['source.css.less', 'meta.selector.css', 'entity.other.attribute-name.id', 'punctuation.definition.entity.css'] 119 | expect(tokens[1]).toEqual value: "abc", scopes: ['source.css.less', 'meta.selector.css', 'entity.other.attribute-name.id'] 120 | 121 | {tokens} = grammar.tokenizeLine("#abc-123 {}") 122 | expect(tokens[0]).toEqual value: "#", scopes: ['source.css.less', 'meta.selector.css', 'entity.other.attribute-name.id', 'punctuation.definition.entity.css'] 123 | expect(tokens[1]).toEqual value: "abc-123", scopes: ['source.css.less', 'meta.selector.css', 'entity.other.attribute-name.id'] 124 | 125 | it "parses custom selectors", -> 126 | {tokens} = grammar.tokenizeLine("abc-123-xyz {}") 127 | expect(tokens[0]).toEqual value: "abc-123-xyz", scopes: ['source.css.less', 'entity.name.tag.custom.css'] 128 | 129 | it "parses pseudo classes", -> 130 | {tokens} = grammar.tokenizeLine(".foo:hover { span:last-of-type { font-weight: bold; } }") 131 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 132 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 133 | expect(tokens[2]).toEqual value: ":", scopes: ['source.css.less', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'] 134 | expect(tokens[3]).toEqual value: "hover", scopes: ['source.css.less', 'entity.other.attribute-name.pseudo-class.css'] 135 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less'] 136 | expect(tokens[5]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 137 | expect(tokens[6]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 138 | expect(tokens[7]).toEqual value: "span", scopes: ['source.css.less', 'meta.property-list.css', 'entity.name.tag.css'] 139 | expect(tokens[8]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'] 140 | expect(tokens[9]).toEqual value: "last-of-type", scopes: ['source.css.less', 'meta.property-list.css', 'entity.other.attribute-name.pseudo-class.css'] 141 | expect(tokens[10]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 142 | expect(tokens[11]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 143 | expect(tokens[12]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-list.css'] 144 | expect(tokens[13]).toEqual value: "font-weight", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-list.css', 'support.type.property-name.css'] 145 | expect(tokens[14]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 146 | 147 | it 'parses nested multiple lines with pseudo-classes', -> 148 | lines = grammar.tokenizeLines ''' 149 | a { p:hover, 150 | p:active { color: blue; } } 151 | ''' 152 | expect(lines[0][0]).toEqual value: 'a', scopes: ['source.css.less', 'entity.name.tag.css'] 153 | expect(lines[0][1]).toEqual value: ' ', scopes: ['source.css.less'] 154 | expect(lines[0][2]).toEqual value: '{', scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 155 | expect(lines[0][3]).toEqual value: ' ', scopes: ['source.css.less', 'meta.property-list.css'] 156 | expect(lines[0][4]).toEqual value: 'p', scopes: ['source.css.less', 'meta.property-list.css', 'entity.name.tag.css'] 157 | expect(lines[0][5]).toEqual value: ':', scopes: ['source.css.less', 'meta.property-list.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'] 158 | expect(lines[0][6]).toEqual value: 'hover', scopes: ['source.css.less', 'meta.property-list.css', 'entity.other.attribute-name.pseudo-class.css'] 159 | expect(lines[0][7]).toEqual value: ',', scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.list.comma.css'] 160 | expect(lines[1][0]).toEqual value: 'p', scopes: ['source.css.less', 'meta.property-list.css', 'entity.name.tag.css'] 161 | expect(lines[1][1]).toEqual value: ':', scopes: ['source.css.less', 'meta.property-list.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'] 162 | expect(lines[1][2]).toEqual value: 'active', scopes: ['source.css.less', 'meta.property-list.css', 'entity.other.attribute-name.pseudo-class.css'] 163 | expect(lines[1][3]).toEqual value: ' ', scopes: ['source.css.less', 'meta.property-list.css'] 164 | expect(lines[1][4]).toEqual value: '{', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 165 | expect(lines[1][5]).toEqual value: ' ', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-list.css'] 166 | 167 | it "parses property lists", -> 168 | {tokens} = grammar.tokenizeLine(".foo { display: table-row; }") 169 | expect(tokens).toHaveLength 12 170 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 171 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 172 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less'] 173 | expect(tokens[3]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 174 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 175 | expect(tokens[5]).toEqual value: "display", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 176 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 177 | expect(tokens[7]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 178 | expect(tokens[8]).toEqual value: "table-row", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 179 | expect(tokens[9]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 180 | expect(tokens[10]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 181 | expect(tokens[11]).toEqual value: "}", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 182 | 183 | it 'parses font lists', -> 184 | {tokens} = grammar.tokenizeLine '.foo { font-family: "Some Font Name", serif; }' 185 | expect(tokens[5]).toEqual value: 'font-family', scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 186 | expect(tokens[9]).toEqual value: 'Some Font Name', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'] 187 | expect(tokens[13]).toEqual value: 'serif', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'] 188 | 189 | it 'parses an incomplete property list', -> 190 | {tokens} = grammar.tokenizeLine '.foo { border: none}' 191 | expect(tokens[5]).toEqual value: 'border', scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 192 | expect(tokens[8]).toEqual value: 'none', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 193 | expect(tokens[9]).toEqual value: '}', scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 194 | 195 | it 'parses multiple lines of an incomplete property-list', -> 196 | lines = grammar.tokenizeLines ''' 197 | very-custom { color: inherit } 198 | another-one { display: none; } 199 | ''' 200 | expect(lines[0][0]).toEqual value: 'very-custom', scopes: ['source.css.less', 'entity.name.tag.custom.css'] 201 | expect(lines[0][4]).toEqual value: 'color', scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 202 | expect(lines[0][7]).toEqual value: 'inherit', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 203 | expect(lines[0][9]).toEqual value: '}', scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 204 | 205 | expect(lines[1][0]).toEqual value: 'another-one', scopes: ['source.css.less', 'entity.name.tag.custom.css'] 206 | expect(lines[1][10]).toEqual value: '}', scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 207 | 208 | it "parses variables", -> 209 | {tokens} = grammar.tokenizeLine(".foo { border: @bar; }") 210 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 211 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 212 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less'] 213 | expect(tokens[3]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 214 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 215 | expect(tokens[5]).toEqual value: "border", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 216 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 217 | expect(tokens[7]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 218 | expect(tokens[8]).toEqual value: "@", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'variable.other.less', 'punctuation.definition.variable.less'] 219 | expect(tokens[9]).toEqual value: "bar", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'variable.other.less'] 220 | expect(tokens[10]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 221 | expect(tokens[11]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 222 | expect(tokens[12]).toEqual value: "}", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 223 | 224 | it "parses css variables", -> 225 | {tokens} = grammar.tokenizeLine(".foo { --spacing-unit: 6px; }") 226 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 227 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 228 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less'] 229 | expect(tokens[3]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 230 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 231 | expect(tokens[5]).toEqual value: "--", scopes: ['source.css.less', 'meta.property-list.css', 'variable.other.less', 'punctuation.definition.variable.less'] 232 | expect(tokens[6]).toEqual value: "spacing-unit", scopes: ['source.css.less', 'meta.property-list.css', 'variable.other.less'] 233 | expect(tokens[7]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 234 | expect(tokens[8]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 235 | expect(tokens[9]).toEqual value: "6", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'] 236 | expect(tokens[10]).toEqual value: "px", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'] 237 | expect(tokens[11]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 238 | expect(tokens[12]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 239 | expect(tokens[13]).toEqual value: "}", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'] 240 | 241 | it 'parses variable interpolation in selectors', -> 242 | {tokens} = grammar.tokenizeLine '.@{selector} { color: #0ee; }' 243 | expect(tokens[0]).toEqual value: '.', scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 244 | expect(tokens[1]).toEqual value: '@{selector}', scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'variable.other.interpolation.less'] 245 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less'] 246 | expect(tokens[3]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 247 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 248 | 249 | it 'parses variable interpolation in properties', -> 250 | {tokens} = grammar.tokenizeLine '.foo { @{property}: #0ee; }' 251 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 252 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 253 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less'] 254 | expect(tokens[3]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 255 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 256 | expect(tokens[5]).toEqual value: '@{property}', scopes: ['source.css.less', 'meta.property-list.css', 'variable.other.interpolation.less'] 257 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 258 | expect(tokens[7]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 259 | 260 | it 'parses variable interpolation in urls', -> 261 | {tokens} = grammar.tokenizeLine '.foo { background: #F0F0F0 url("@{var}/img.png"); }";' 262 | expect(tokens[8]).toEqual value: "#F0F0F0", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.rgb-value.css'] 263 | expect(tokens[10]).toEqual value: "url", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css'] 264 | expect(tokens[11]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'meta.brace.round.css'] 265 | expect(tokens[13]).toEqual value: "@{var}", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'string.quoted.double.css', 'variable.other.interpolation.less'] 266 | expect(tokens[14]).toEqual value: "/img.png", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'string.quoted.double.css'] 267 | 268 | it 'parses variable interpolation in imports', -> 269 | {tokens} = grammar.tokenizeLine '@import "@{var}/tidal-wave.less";' 270 | expect(tokens[0]).toEqual value: "@", scopes: ['source.css.less', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.less', 'punctuation.definition.keyword.less'] 271 | expect(tokens[1]).toEqual value: "import", scopes: ['source.css.less', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.less'] 272 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less', 'meta.at-rule.import.css'] 273 | expect(tokens[3]).toEqual value: "\"", scopes: ['source.css.less', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'] 274 | expect(tokens[4]).toEqual value: "@{var}", scopes: ['source.css.less', 'meta.at-rule.import.css', 'string.quoted.double.css', 'variable.other.interpolation.less'] 275 | 276 | it 'parses options in import statements', -> 277 | {tokens} = grammar.tokenizeLine '@import (optional, reference) "theme";' 278 | expect(tokens[0]).toEqual value: "@", scopes: ['source.css.less', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.less', 'punctuation.definition.keyword.less'] 279 | expect(tokens[1]).toEqual value: "import", scopes: ['source.css.less', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.less'] 280 | expect(tokens[4]).toEqual value: "optional", scopes: ['source.css.less', 'meta.at-rule.import.css', 'keyword.control.import.option.less'] 281 | expect(tokens[5]).toEqual value: ",", scopes: ['source.css.less', 'meta.at-rule.import.css', 'punctuation.separator.list.comma.css'] 282 | expect(tokens[7]).toEqual value: "reference", scopes: ['source.css.less', 'meta.at-rule.import.css', 'keyword.control.import.option.less'] 283 | expect(tokens[11]).toEqual value: "theme", scopes: ['source.css.less', 'meta.at-rule.import.css', 'string.quoted.double.css'] 284 | 285 | it 'parses built-in functions in property values', -> 286 | {tokens} = grammar.tokenizeLine '.foo { border: 1px solid rgba(0,0,0); }' 287 | expect(tokens[0]).toEqual value: ".", scopes: ['source.css.less', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'] 288 | expect(tokens[1]).toEqual value: "foo", scopes: ['source.css.less', 'entity.other.attribute-name.class.css'] 289 | expect(tokens[2]).toEqual value: " ", scopes: ['source.css.less'] 290 | expect(tokens[3]).toEqual value: "{", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'] 291 | expect(tokens[4]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 292 | expect(tokens[5]).toEqual value: "border", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 293 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 294 | expect(tokens[8]).toEqual value: "1", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'] 295 | expect(tokens[9]).toEqual value: "px", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'] 296 | expect(tokens[11]).toEqual value: "solid", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 297 | expect(tokens[13]).toEqual value: "rgba", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'] 298 | expect(tokens[14]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'] 299 | expect(tokens[15]).toEqual value: "0", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'] 300 | expect(tokens[16]).toEqual value: ",", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'] 301 | expect(tokens[17]).toEqual value: "0", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'] 302 | expect(tokens[18]).toEqual value: ",", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'] 303 | expect(tokens[21]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 304 | 305 | it 'parses linear-gradient', -> 306 | {tokens} = grammar.tokenizeLine '.foo { background: linear-gradient(white, black); }' 307 | expect(tokens[5]).toEqual value: "background", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 308 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 309 | expect(tokens[7]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 310 | expect(tokens[8]).toEqual value: "linear-gradient", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'] 311 | expect(tokens[9]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'] 312 | 313 | it 'parses transform functions', -> 314 | {tokens} = grammar.tokenizeLine '.foo { transform: scaleY(1); }' 315 | expect(tokens[5]).toEqual value: "transform", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 316 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 317 | expect(tokens[7]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 318 | expect(tokens[8]).toEqual value: "scaleY", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'] 319 | expect(tokens[9]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.section.function.begin.bracket.round.css'] 320 | 321 | it 'parses blend modes', -> 322 | {tokens} = grammar.tokenizeLine '.foo { background-blend-mode: color-dodge; }' 323 | expect(tokens[5]).toEqual value: "background-blend-mode", scopes: ['source.css.less', 'meta.property-list.css', 'support.type.property-name.css'] 324 | expect(tokens[6]).toEqual value: ":", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.separator.key-value.css'] 325 | expect(tokens[7]).toEqual value: " ", scopes: ['source.css.less', 'meta.property-list.css'] 326 | expect(tokens[8]).toEqual value: "color-dodge", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'] 327 | expect(tokens[9]).toEqual value: ";", scopes: ['source.css.less', 'meta.property-list.css', 'punctuation.terminator.rule.css'] 328 | 329 | it 'parses non-quoted urls', -> 330 | {tokens} = grammar.tokenizeLine '.foo { background: url(http://%20/2.png) }' 331 | expect(tokens[8]).toEqual value: "url", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css'] 332 | expect(tokens[9]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'meta.brace.round.css'] 333 | expect(tokens[10]).toEqual value: "http://%20/2.png", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'string.url.css'] 334 | 335 | it 'parses non-quoted relative urls', -> 336 | {tokens} = grammar.tokenizeLine '.foo { background: url(../path/to/image.png) }' 337 | expect(tokens[8]).toEqual value: "url", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css'] 338 | expect(tokens[9]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'meta.brace.round.css'] 339 | expect(tokens[10]).toEqual value: "../path/to/image.png", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'string.url.css'] 340 | 341 | it 'parses non-quoted urls followed by a format', -> 342 | {tokens} = grammar.tokenizeLine '@font-face { src: url(http://example.com/font.woff) format("woff"); }' 343 | expect(tokens[8]).toEqual value: 'url', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css'] 344 | expect(tokens[9]).toEqual value: "(", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'meta.brace.round.css'] 345 | expect(tokens[10]).toEqual value: "http://example.com/font.woff", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'string.url.css'] 346 | expect(tokens[11]).toEqual value: ")", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.url.css', 'meta.brace.round.css'] 347 | expect(tokens[13]).toEqual value: "format", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'support.function.any-method.builtin.less'] 348 | 349 | it 'parses the "true" value', -> 350 | {tokens} = grammar.tokenizeLine '@var: true;' 351 | expect(tokens[4]).toEqual value: "true", scopes: ['source.css.less', 'constant.language.boolean.less'] 352 | 353 | it 'parses mixin guards', -> 354 | {tokens} = grammar.tokenizeLine '.mixin() when (isnumber(@b)) and (default()), (ispixel(@a)) and not (@a < 0) { }' 355 | expect(tokens[4]).toEqual value: "when", scopes: ['source.css.less', 'keyword.control.logical.operator.less'] 356 | expect(tokens[7]).toEqual value: "isnumber", scopes: ['source.css.less', 'support.function.type-checking.less'] 357 | expect(tokens[14]).toEqual value: "and", scopes: ['source.css.less', 'keyword.control.logical.operator.less'] 358 | expect(tokens[17]).toEqual value: "default", scopes: ['source.css.less', 'support.function.default.less'] 359 | expect(tokens[21]).toEqual value: ",", scopes: ['source.css.less', 'punctuation.separator.list.comma.css'] 360 | expect(tokens[24]).toEqual value: "ispixel", scopes: ['source.css.less', 'support.function.unit-checking.less'] 361 | expect(tokens[31]).toEqual value: "and", scopes: ['source.css.less', 'keyword.control.logical.operator.less'] 362 | expect(tokens[33]).toEqual value: "not", scopes: ['source.css.less', 'keyword.control.logical.operator.less'] 363 | expect(tokens[39]).toEqual value: "<", scopes: ['source.css.less', 'keyword.operator.less'] 364 | 365 | describe 'strings', -> 366 | it 'tokenizes single-quote strings', -> 367 | {tokens} = grammar.tokenizeLine ".a { content: 'hi' }" 368 | 369 | expect(tokens[8]).toEqual value: "'", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'] 370 | expect(tokens[9]).toEqual value: 'hi', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css'] 371 | expect(tokens[10]).toEqual value: "'", scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'] 372 | 373 | it 'tokenizes double-quote strings', -> 374 | {tokens} = grammar.tokenizeLine '.a { content: "hi" }' 375 | 376 | expect(tokens[8]).toEqual value: '"', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'] 377 | expect(tokens[9]).toEqual value: 'hi', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'] 378 | expect(tokens[10]).toEqual value: '"', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'] 379 | 380 | it 'tokenizes escape characters', -> 381 | {tokens} = grammar.tokenizeLine ".a { content: '\\abcdef' }" 382 | 383 | expect(tokens[9]).toEqual value: '\\abcdef', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.css'] 384 | 385 | {tokens} = grammar.tokenizeLine '.a { content: "\\abcdef" }' 386 | 387 | expect(tokens[9]).toEqual value: '\\abcdef', scopes: ['source.css.less', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.css'] 388 | -------------------------------------------------------------------------------- /update.coffee: -------------------------------------------------------------------------------- 1 | # Run this to update the list of builtin less functions 2 | 3 | path = require 'path' 4 | request = require 'request' 5 | Promise = require 'bluebird' 6 | CSON = require 'season' 7 | 8 | FunctionsURL = 'https://raw.githubusercontent.com/less/less-docs/master/content/functions/data/functions.json' 9 | 10 | functionsPromise = new Promise (resolve) -> 11 | request {json: true, url: FunctionsURL}, (error, response, properties) -> 12 | if error? 13 | console.error(error.message) 14 | resolve(null) 15 | if response.statusCode isnt 200 16 | console.error("Request failed: #{response.statusCode}") 17 | resolve(null) 18 | resolve(properties) 19 | 20 | functionsPromise.then (results) -> 21 | suggestions = [] 22 | for functionType, functions of results 23 | for func in functions 24 | suggestions.push 25 | type: 'function' 26 | rightLabel: 'Less Builtin' 27 | snippet: sanitizeFunc(func.example) 28 | description: func.description 29 | descriptionMoreURL: "http://lesscss.org/functions/##{functionType}-#{func.name}" 30 | 31 | configPath = path.join(__dirname, 'settings', 'language-less.cson') 32 | config = CSON.readFileSync(configPath) 33 | builtins = config['.source.css.less .meta.property-value'].autocomplete.symbols.builtins 34 | builtins.suggestions = suggestions 35 | CSON.writeFileSync(configPath, config) 36 | 37 | sanitizeFunc = (functionExample) -> 38 | functionExample = functionExample.replace(';', '') 39 | functionExample = functionExample.replace(/\[, /g, ', [') 40 | functionExample = functionExample.replace(/\,] /g, '], ') 41 | 42 | argsRe = /\(([^\)]+)\)/ 43 | functionExample = functionExample.replace argsRe, (args) -> 44 | args = argsRe.exec(args)[1] 45 | args = args.split(',') 46 | args = ("${#{index + 1}:#{arg.trim()}}" for arg, index in args) 47 | "(#{args.join(', ')})${#{index+1}:;}" 48 | 49 | "#{functionExample}$0" 50 | --------------------------------------------------------------------------------