less.js 6.2 KB

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