less.js 4.8 KB

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