less.js 5.0 KB

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