less.js 4.8 KB

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