pug.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'), require('../javascript/javascript'), require('../css/css'), require('../htmlmixed/htmlmixed'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror', '../javascript/javascript', '../css/css', '../htmlmixed/htmlmixed'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. CodeMirror.defineMode(
  15. 'pug',
  16. function (config) {
  17. // token types
  18. var KEYWORD = 'keyword'
  19. var DOCTYPE = 'meta'
  20. var ID = 'builtin'
  21. var CLASS = 'qualifier'
  22. var ATTRS_NEST = {
  23. '{': '}',
  24. '(': ')',
  25. '[': ']',
  26. }
  27. var jsMode = CodeMirror.getMode(config, 'javascript')
  28. function State() {
  29. this.javaScriptLine = false
  30. this.javaScriptLineExcludesColon = false
  31. this.javaScriptArguments = false
  32. this.javaScriptArgumentsDepth = 0
  33. this.isInterpolating = false
  34. this.interpolationNesting = 0
  35. this.jsState = CodeMirror.startState(jsMode)
  36. this.restOfLine = ''
  37. this.isIncludeFiltered = false
  38. this.isEach = false
  39. this.lastTag = ''
  40. this.scriptType = ''
  41. // Attributes Mode
  42. this.isAttrs = false
  43. this.attrsNest = []
  44. this.inAttributeName = true
  45. this.attributeIsType = false
  46. this.attrValue = ''
  47. // Indented Mode
  48. this.indentOf = Infinity
  49. this.indentToken = ''
  50. this.innerMode = null
  51. this.innerState = null
  52. this.innerModeForLine = false
  53. }
  54. /**
  55. * Safely copy a state
  56. *
  57. * @return {State}
  58. */
  59. State.prototype.copy = function () {
  60. var res = new State()
  61. res.javaScriptLine = this.javaScriptLine
  62. res.javaScriptLineExcludesColon = this.javaScriptLineExcludesColon
  63. res.javaScriptArguments = this.javaScriptArguments
  64. res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth
  65. res.isInterpolating = this.isInterpolating
  66. res.interpolationNesting = this.interpolationNesting
  67. res.jsState = CodeMirror.copyState(jsMode, this.jsState)
  68. res.innerMode = this.innerMode
  69. if (this.innerMode && this.innerState) {
  70. res.innerState = CodeMirror.copyState(this.innerMode, this.innerState)
  71. }
  72. res.restOfLine = this.restOfLine
  73. res.isIncludeFiltered = this.isIncludeFiltered
  74. res.isEach = this.isEach
  75. res.lastTag = this.lastTag
  76. res.scriptType = this.scriptType
  77. res.isAttrs = this.isAttrs
  78. res.attrsNest = this.attrsNest.slice()
  79. res.inAttributeName = this.inAttributeName
  80. res.attributeIsType = this.attributeIsType
  81. res.attrValue = this.attrValue
  82. res.indentOf = this.indentOf
  83. res.indentToken = this.indentToken
  84. res.innerModeForLine = this.innerModeForLine
  85. return res
  86. }
  87. function javaScript(stream, state) {
  88. if (stream.sol()) {
  89. // if javaScriptLine was set at end of line, ignore it
  90. state.javaScriptLine = false
  91. state.javaScriptLineExcludesColon = false
  92. }
  93. if (state.javaScriptLine) {
  94. if (state.javaScriptLineExcludesColon && stream.peek() === ':') {
  95. state.javaScriptLine = false
  96. state.javaScriptLineExcludesColon = false
  97. return
  98. }
  99. var tok = jsMode.token(stream, state.jsState)
  100. if (stream.eol()) state.javaScriptLine = false
  101. return tok || true
  102. }
  103. }
  104. function javaScriptArguments(stream, state) {
  105. if (state.javaScriptArguments) {
  106. if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') {
  107. state.javaScriptArguments = false
  108. return
  109. }
  110. if (stream.peek() === '(') {
  111. state.javaScriptArgumentsDepth++
  112. } else if (stream.peek() === ')') {
  113. state.javaScriptArgumentsDepth--
  114. }
  115. if (state.javaScriptArgumentsDepth === 0) {
  116. state.javaScriptArguments = false
  117. return
  118. }
  119. var tok = jsMode.token(stream, state.jsState)
  120. return tok || true
  121. }
  122. }
  123. function yieldStatement(stream) {
  124. if (stream.match(/^yield\b/)) {
  125. return 'keyword'
  126. }
  127. }
  128. function doctype(stream) {
  129. if (stream.match(/^(?:doctype) *([^\n]+)?/)) {
  130. return DOCTYPE
  131. }
  132. }
  133. function interpolation(stream, state) {
  134. if (stream.match('#{')) {
  135. state.isInterpolating = true
  136. state.interpolationNesting = 0
  137. return 'punctuation'
  138. }
  139. }
  140. function interpolationContinued(stream, state) {
  141. if (state.isInterpolating) {
  142. if (stream.peek() === '}') {
  143. state.interpolationNesting--
  144. if (state.interpolationNesting < 0) {
  145. stream.next()
  146. state.isInterpolating = false
  147. return 'punctuation'
  148. }
  149. } else if (stream.peek() === '{') {
  150. state.interpolationNesting++
  151. }
  152. return jsMode.token(stream, state.jsState) || true
  153. }
  154. }
  155. function caseStatement(stream, state) {
  156. if (stream.match(/^case\b/)) {
  157. state.javaScriptLine = true
  158. return KEYWORD
  159. }
  160. }
  161. function when(stream, state) {
  162. if (stream.match(/^when\b/)) {
  163. state.javaScriptLine = true
  164. state.javaScriptLineExcludesColon = true
  165. return KEYWORD
  166. }
  167. }
  168. function defaultStatement(stream) {
  169. if (stream.match(/^default\b/)) {
  170. return KEYWORD
  171. }
  172. }
  173. function extendsStatement(stream, state) {
  174. if (stream.match(/^extends?\b/)) {
  175. state.restOfLine = 'string'
  176. return KEYWORD
  177. }
  178. }
  179. function append(stream, state) {
  180. if (stream.match(/^append\b/)) {
  181. state.restOfLine = 'variable'
  182. return KEYWORD
  183. }
  184. }
  185. function prepend(stream, state) {
  186. if (stream.match(/^prepend\b/)) {
  187. state.restOfLine = 'variable'
  188. return KEYWORD
  189. }
  190. }
  191. function block(stream, state) {
  192. if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) {
  193. state.restOfLine = 'variable'
  194. return KEYWORD
  195. }
  196. }
  197. function include(stream, state) {
  198. if (stream.match(/^include\b/)) {
  199. state.restOfLine = 'string'
  200. return KEYWORD
  201. }
  202. }
  203. function includeFiltered(stream, state) {
  204. if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) {
  205. state.isIncludeFiltered = true
  206. return KEYWORD
  207. }
  208. }
  209. function includeFilteredContinued(stream, state) {
  210. if (state.isIncludeFiltered) {
  211. var tok = filter(stream, state)
  212. state.isIncludeFiltered = false
  213. state.restOfLine = 'string'
  214. return tok
  215. }
  216. }
  217. function mixin(stream, state) {
  218. if (stream.match(/^mixin\b/)) {
  219. state.javaScriptLine = true
  220. return KEYWORD
  221. }
  222. }
  223. function call(stream, state) {
  224. if (stream.match(/^\+([-\w]+)/)) {
  225. if (!stream.match(/^\( *[-\w]+ *=/, false)) {
  226. state.javaScriptArguments = true
  227. state.javaScriptArgumentsDepth = 0
  228. }
  229. return 'variable'
  230. }
  231. if (stream.match('+#{', false)) {
  232. stream.next()
  233. state.mixinCallAfter = true
  234. return interpolation(stream, state)
  235. }
  236. }
  237. function callArguments(stream, state) {
  238. if (state.mixinCallAfter) {
  239. state.mixinCallAfter = false
  240. if (!stream.match(/^\( *[-\w]+ *=/, false)) {
  241. state.javaScriptArguments = true
  242. state.javaScriptArgumentsDepth = 0
  243. }
  244. return true
  245. }
  246. }
  247. function conditional(stream, state) {
  248. if (stream.match(/^(if|unless|else if|else)\b/)) {
  249. state.javaScriptLine = true
  250. return KEYWORD
  251. }
  252. }
  253. function each(stream, state) {
  254. if (stream.match(/^(- *)?(each|for)\b/)) {
  255. state.isEach = true
  256. return KEYWORD
  257. }
  258. }
  259. function eachContinued(stream, state) {
  260. if (state.isEach) {
  261. if (stream.match(/^ in\b/)) {
  262. state.javaScriptLine = true
  263. state.isEach = false
  264. return KEYWORD
  265. } else if (stream.sol() || stream.eol()) {
  266. state.isEach = false
  267. } else if (stream.next()) {
  268. while (!stream.match(/^ in\b/, false) && stream.next());
  269. return 'variable'
  270. }
  271. }
  272. }
  273. function whileStatement(stream, state) {
  274. if (stream.match(/^while\b/)) {
  275. state.javaScriptLine = true
  276. return KEYWORD
  277. }
  278. }
  279. function tag(stream, state) {
  280. var captures
  281. if ((captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/))) {
  282. state.lastTag = captures[1].toLowerCase()
  283. if (state.lastTag === 'script') {
  284. state.scriptType = 'application/javascript'
  285. }
  286. return 'tag'
  287. }
  288. }
  289. function filter(stream, state) {
  290. if (stream.match(/^:([\w\-]+)/)) {
  291. var innerMode
  292. if (config && config.innerModes) {
  293. innerMode = config.innerModes(stream.current().substring(1))
  294. }
  295. if (!innerMode) {
  296. innerMode = stream.current().substring(1)
  297. }
  298. if (typeof innerMode === 'string') {
  299. innerMode = CodeMirror.getMode(config, innerMode)
  300. }
  301. setInnerMode(stream, state, innerMode)
  302. return 'atom'
  303. }
  304. }
  305. function code(stream, state) {
  306. if (stream.match(/^(!?=|-)/)) {
  307. state.javaScriptLine = true
  308. return 'punctuation'
  309. }
  310. }
  311. function id(stream) {
  312. if (stream.match(/^#([\w-]+)/)) {
  313. return ID
  314. }
  315. }
  316. function className(stream) {
  317. if (stream.match(/^\.([\w-]+)/)) {
  318. return CLASS
  319. }
  320. }
  321. function attrs(stream, state) {
  322. if (stream.peek() == '(') {
  323. stream.next()
  324. state.isAttrs = true
  325. state.attrsNest = []
  326. state.inAttributeName = true
  327. state.attrValue = ''
  328. state.attributeIsType = false
  329. return 'punctuation'
  330. }
  331. }
  332. function attrsContinued(stream, state) {
  333. if (state.isAttrs) {
  334. if (ATTRS_NEST[stream.peek()]) {
  335. state.attrsNest.push(ATTRS_NEST[stream.peek()])
  336. }
  337. if (state.attrsNest[state.attrsNest.length - 1] === stream.peek()) {
  338. state.attrsNest.pop()
  339. } else if (stream.eat(')')) {
  340. state.isAttrs = false
  341. return 'punctuation'
  342. }
  343. if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) {
  344. if (stream.peek() === '=' || stream.peek() === '!') {
  345. state.inAttributeName = false
  346. state.jsState = CodeMirror.startState(jsMode)
  347. if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') {
  348. state.attributeIsType = true
  349. } else {
  350. state.attributeIsType = false
  351. }
  352. }
  353. return 'attribute'
  354. }
  355. var tok = jsMode.token(stream, state.jsState)
  356. if (state.attributeIsType && tok === 'string') {
  357. state.scriptType = stream.current().toString()
  358. }
  359. if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) {
  360. try {
  361. Function('', 'var x ' + state.attrValue.replace(/,\s*$/, '').replace(/^!/, ''))
  362. state.inAttributeName = true
  363. state.attrValue = ''
  364. stream.backUp(stream.current().length)
  365. return attrsContinued(stream, state)
  366. } catch (ex) {
  367. //not the end of an attribute
  368. }
  369. }
  370. state.attrValue += stream.current()
  371. return tok || true
  372. }
  373. }
  374. function attributesBlock(stream, state) {
  375. if (stream.match(/^&attributes\b/)) {
  376. state.javaScriptArguments = true
  377. state.javaScriptArgumentsDepth = 0
  378. return 'keyword'
  379. }
  380. }
  381. function indent(stream) {
  382. if (stream.sol() && stream.eatSpace()) {
  383. return 'indent'
  384. }
  385. }
  386. function comment(stream, state) {
  387. if (stream.match(/^ *\/\/(-)?([^\n]*)/)) {
  388. state.indentOf = stream.indentation()
  389. state.indentToken = 'comment'
  390. return 'comment'
  391. }
  392. }
  393. function colon(stream) {
  394. if (stream.match(/^: */)) {
  395. return 'colon'
  396. }
  397. }
  398. function text(stream, state) {
  399. if (stream.match(/^(?:\| ?| )([^\n]+)/)) {
  400. return 'string'
  401. }
  402. if (stream.match(/^(<[^\n]*)/, false)) {
  403. // html string
  404. setInnerMode(stream, state, 'htmlmixed')
  405. state.innerModeForLine = true
  406. return innerMode(stream, state, true)
  407. }
  408. }
  409. function dot(stream, state) {
  410. if (stream.eat('.')) {
  411. var innerMode = null
  412. if (state.lastTag === 'script' && state.scriptType.toLowerCase().indexOf('javascript') != -1) {
  413. innerMode = state.scriptType.toLowerCase().replace(/"|'/g, '')
  414. } else if (state.lastTag === 'style') {
  415. innerMode = 'css'
  416. }
  417. setInnerMode(stream, state, innerMode)
  418. return 'dot'
  419. }
  420. }
  421. function fail(stream) {
  422. stream.next()
  423. return null
  424. }
  425. function setInnerMode(stream, state, mode) {
  426. mode = CodeMirror.mimeModes[mode] || mode
  427. mode = config.innerModes ? config.innerModes(mode) || mode : mode
  428. mode = CodeMirror.mimeModes[mode] || mode
  429. mode = CodeMirror.getMode(config, mode)
  430. state.indentOf = stream.indentation()
  431. if (mode && mode.name !== 'null') {
  432. state.innerMode = mode
  433. } else {
  434. state.indentToken = 'string'
  435. }
  436. }
  437. function innerMode(stream, state, force) {
  438. if (stream.indentation() > state.indentOf || (state.innerModeForLine && !stream.sol()) || force) {
  439. if (state.innerMode) {
  440. if (!state.innerState) {
  441. state.innerState = state.innerMode.startState ? CodeMirror.startState(state.innerMode, stream.indentation()) : {}
  442. }
  443. return stream.hideFirstChars(state.indentOf + 2, function () {
  444. return state.innerMode.token(stream, state.innerState) || true
  445. })
  446. } else {
  447. stream.skipToEnd()
  448. return state.indentToken
  449. }
  450. } else if (stream.sol()) {
  451. state.indentOf = Infinity
  452. state.indentToken = null
  453. state.innerMode = null
  454. state.innerState = null
  455. }
  456. }
  457. function restOfLine(stream, state) {
  458. if (stream.sol()) {
  459. // if restOfLine was set at end of line, ignore it
  460. state.restOfLine = ''
  461. }
  462. if (state.restOfLine) {
  463. stream.skipToEnd()
  464. var tok = state.restOfLine
  465. state.restOfLine = ''
  466. return tok
  467. }
  468. }
  469. function startState() {
  470. return new State()
  471. }
  472. function copyState(state) {
  473. return state.copy()
  474. }
  475. /**
  476. * Get the next token in the stream
  477. *
  478. * @param {Stream} stream
  479. * @param {State} state
  480. */
  481. function nextToken(stream, state) {
  482. var tok =
  483. innerMode(stream, state) ||
  484. restOfLine(stream, state) ||
  485. interpolationContinued(stream, state) ||
  486. includeFilteredContinued(stream, state) ||
  487. eachContinued(stream, state) ||
  488. attrsContinued(stream, state) ||
  489. javaScript(stream, state) ||
  490. javaScriptArguments(stream, state) ||
  491. callArguments(stream, state) ||
  492. yieldStatement(stream) ||
  493. doctype(stream) ||
  494. interpolation(stream, state) ||
  495. caseStatement(stream, state) ||
  496. when(stream, state) ||
  497. defaultStatement(stream) ||
  498. extendsStatement(stream, state) ||
  499. append(stream, state) ||
  500. prepend(stream, state) ||
  501. block(stream, state) ||
  502. include(stream, state) ||
  503. includeFiltered(stream, state) ||
  504. mixin(stream, state) ||
  505. call(stream, state) ||
  506. conditional(stream, state) ||
  507. each(stream, state) ||
  508. whileStatement(stream, state) ||
  509. tag(stream, state) ||
  510. filter(stream, state) ||
  511. code(stream, state) ||
  512. id(stream) ||
  513. className(stream) ||
  514. attrs(stream, state) ||
  515. attributesBlock(stream, state) ||
  516. indent(stream) ||
  517. text(stream, state) ||
  518. comment(stream, state) ||
  519. colon(stream) ||
  520. dot(stream, state) ||
  521. fail(stream)
  522. return tok === true ? null : tok
  523. }
  524. return {
  525. startState: startState,
  526. copyState: copyState,
  527. token: nextToken,
  528. }
  529. },
  530. 'javascript',
  531. 'css',
  532. 'htmlmixed'
  533. )
  534. CodeMirror.defineMIME('text/x-pug', 'pug')
  535. CodeMirror.defineMIME('text/x-jade', 'pug')
  536. })