less.js (5069B)
1 /* 2 Language: Less 3 Description: It's CSS, with just a little more. 4 Author: Max Mikhailov <seven.phases.max@gmail.com> 5 Website: http://lesscss.org 6 Category: common, css 7 */ 8 9 function less(hljs) { 10 var IDENT_RE = '[\\w-]+'; // yes, Less identifiers may begin with a digit 11 var INTERP_IDENT_RE = '(' + IDENT_RE + '|@{' + IDENT_RE + '})'; 12 13 /* Generic Modes */ 14 15 var RULES = [], VALUE = []; // forward def. for recursive modes 16 17 var STRING_MODE = function(c) { return { 18 // Less strings are not multiline (also include '~' for more consistent coloring of "escaped" strings) 19 className: 'string', begin: '~?' + c + '.*?' + c 20 };}; 21 22 var IDENT_MODE = function(name, begin, relevance) { return { 23 className: name, begin: begin, relevance: relevance 24 };}; 25 26 var PARENS_MODE = { 27 // used only to properly balance nested parens inside mixin call, def. arg list 28 begin: '\\(', end: '\\)', contains: VALUE, relevance: 0 29 }; 30 31 // generic Less highlighter (used almost everywhere except selectors): 32 VALUE.push( 33 hljs.C_LINE_COMMENT_MODE, 34 hljs.C_BLOCK_COMMENT_MODE, 35 STRING_MODE("'"), 36 STRING_MODE('"'), 37 hljs.CSS_NUMBER_MODE, // fixme: it does not include dot for numbers like .5em :( 38 { 39 begin: '(url|data-uri)\\(', 40 starts: {className: 'string', end: '[\\)\\n]', excludeEnd: true} 41 }, 42 IDENT_MODE('number', '#[0-9A-Fa-f]+\\b'), 43 PARENS_MODE, 44 IDENT_MODE('variable', '@@?' + IDENT_RE, 10), 45 IDENT_MODE('variable', '@{' + IDENT_RE + '}'), 46 IDENT_MODE('built_in', '~?`[^`]*?`'), // inline javascript (or whatever host language) *multiline* string 47 { // @media features (it’s here to not duplicate things in AT_RULE_MODE with extra PARENS_MODE overriding): 48 className: 'attribute', begin: IDENT_RE + '\\s*:', end: ':', returnBegin: true, excludeEnd: true 49 }, 50 { 51 className: 'meta', 52 begin: '!important' 53 } 54 ); 55 56 var VALUE_WITH_RULESETS = VALUE.concat({ 57 begin: '{', end: '}', contains: RULES 58 }); 59 60 var MIXIN_GUARD_MODE = { 61 beginKeywords: 'when', endsWithParent: true, 62 contains: [{beginKeywords: 'and not'}].concat(VALUE) // using this form to override VALUE’s 'function' match 63 }; 64 65 /* Rule-Level Modes */ 66 67 var RULE_MODE = { 68 begin: INTERP_IDENT_RE + '\\s*:', returnBegin: true, end: '[;}]', 69 relevance: 0, 70 contains: [ 71 { 72 className: 'attribute', 73 begin: INTERP_IDENT_RE, end: ':', excludeEnd: true, 74 starts: { 75 endsWithParent: true, illegal: '[<=$]', 76 relevance: 0, 77 contains: VALUE 78 } 79 } 80 ] 81 }; 82 83 var AT_RULE_MODE = { 84 className: 'keyword', 85 begin: '@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b', 86 starts: {end: '[;{}]', returnEnd: true, contains: VALUE, relevance: 0} 87 }; 88 89 // variable definitions and calls 90 var VAR_RULE_MODE = { 91 className: 'variable', 92 variants: [ 93 // using more strict pattern for higher relevance to increase chances of Less detection. 94 // this is *the only* Less specific statement used in most of the sources, so... 95 // (we’ll still often loose to the css-parser unless there's '//' comment, 96 // simply because 1 variable just can't beat 99 properties :) 97 {begin: '@' + IDENT_RE + '\\s*:', relevance: 15}, 98 {begin: '@' + IDENT_RE} 99 ], 100 starts: {end: '[;}]', returnEnd: true, contains: VALUE_WITH_RULESETS} 101 }; 102 103 var SELECTOR_MODE = { 104 // first parse unambiguous selectors (i.e. those not starting with tag) 105 // then fall into the scary lookahead-discriminator variant. 106 // this mode also handles mixin definitions and calls 107 variants: [{ 108 begin: '[\\.#:&\\[>]', end: '[;{}]' // mixin calls end with ';' 109 }, { 110 begin: INTERP_IDENT_RE, end: '{' 111 }], 112 returnBegin: true, 113 returnEnd: true, 114 illegal: '[<=\'$"]', 115 relevance: 0, 116 contains: [ 117 hljs.C_LINE_COMMENT_MODE, 118 hljs.C_BLOCK_COMMENT_MODE, 119 MIXIN_GUARD_MODE, 120 IDENT_MODE('keyword', 'all\\b'), 121 IDENT_MODE('variable', '@{' + IDENT_RE + '}'), // otherwise it’s identified as tag 122 IDENT_MODE('selector-tag', INTERP_IDENT_RE + '%?', 0), // '%' for more consistent coloring of @keyframes "tags" 123 IDENT_MODE('selector-id', '#' + INTERP_IDENT_RE), 124 IDENT_MODE('selector-class', '\\.' + INTERP_IDENT_RE, 0), 125 IDENT_MODE('selector-tag', '&', 0), 126 {className: 'selector-attr', begin: '\\[', end: '\\]'}, 127 {className: 'selector-pseudo', begin: /:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/}, 128 {begin: '\\(', end: '\\)', contains: VALUE_WITH_RULESETS}, // argument list of parametric mixins 129 {begin: '!important'} // eat !important after mixin call or it will be colored as tag 130 ] 131 }; 132 133 RULES.push( 134 hljs.C_LINE_COMMENT_MODE, 135 hljs.C_BLOCK_COMMENT_MODE, 136 AT_RULE_MODE, 137 VAR_RULE_MODE, 138 RULE_MODE, 139 SELECTOR_MODE 140 ); 141 142 return { 143 name: 'Less', 144 case_insensitive: true, 145 illegal: '[=>\'/<($"]', 146 contains: RULES 147 }; 148 } 149 150 module.exports = less;