django.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  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('../htmlmixed/htmlmixed'), require('../../addon/mode/overlay'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror', '../htmlmixed/htmlmixed', '../../addon/mode/overlay'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. CodeMirror.defineMode('django:inner', function () {
  15. var keywords = [
  16. 'block',
  17. 'endblock',
  18. 'for',
  19. 'endfor',
  20. 'true',
  21. 'false',
  22. 'filter',
  23. 'endfilter',
  24. 'loop',
  25. 'none',
  26. 'self',
  27. 'super',
  28. 'if',
  29. 'elif',
  30. 'endif',
  31. 'as',
  32. 'else',
  33. 'import',
  34. 'with',
  35. 'endwith',
  36. 'without',
  37. 'context',
  38. 'ifequal',
  39. 'endifequal',
  40. 'ifnotequal',
  41. 'endifnotequal',
  42. 'extends',
  43. 'include',
  44. 'load',
  45. 'comment',
  46. 'endcomment',
  47. 'empty',
  48. 'url',
  49. 'static',
  50. 'trans',
  51. 'blocktrans',
  52. 'endblocktrans',
  53. 'now',
  54. 'regroup',
  55. 'lorem',
  56. 'ifchanged',
  57. 'endifchanged',
  58. 'firstof',
  59. 'debug',
  60. 'cycle',
  61. 'csrf_token',
  62. 'autoescape',
  63. 'endautoescape',
  64. 'spaceless',
  65. 'endspaceless',
  66. 'ssi',
  67. 'templatetag',
  68. 'verbatim',
  69. 'endverbatim',
  70. 'widthratio',
  71. ],
  72. filters = [
  73. 'add',
  74. 'addslashes',
  75. 'capfirst',
  76. 'center',
  77. 'cut',
  78. 'date',
  79. 'default',
  80. 'default_if_none',
  81. 'dictsort',
  82. 'dictsortreversed',
  83. 'divisibleby',
  84. 'escape',
  85. 'escapejs',
  86. 'filesizeformat',
  87. 'first',
  88. 'floatformat',
  89. 'force_escape',
  90. 'get_digit',
  91. 'iriencode',
  92. 'join',
  93. 'last',
  94. 'length',
  95. 'length_is',
  96. 'linebreaks',
  97. 'linebreaksbr',
  98. 'linenumbers',
  99. 'ljust',
  100. 'lower',
  101. 'make_list',
  102. 'phone2numeric',
  103. 'pluralize',
  104. 'pprint',
  105. 'random',
  106. 'removetags',
  107. 'rjust',
  108. 'safe',
  109. 'safeseq',
  110. 'slice',
  111. 'slugify',
  112. 'stringformat',
  113. 'striptags',
  114. 'time',
  115. 'timesince',
  116. 'timeuntil',
  117. 'title',
  118. 'truncatechars',
  119. 'truncatechars_html',
  120. 'truncatewords',
  121. 'truncatewords_html',
  122. 'unordered_list',
  123. 'upper',
  124. 'urlencode',
  125. 'urlize',
  126. 'urlizetrunc',
  127. 'wordcount',
  128. 'wordwrap',
  129. 'yesno',
  130. ],
  131. operators = ['==', '!=', '<', '>', '<=', '>='],
  132. wordOperators = ['in', 'not', 'or', 'and']
  133. keywords = new RegExp('^\\b(' + keywords.join('|') + ')\\b')
  134. filters = new RegExp('^\\b(' + filters.join('|') + ')\\b')
  135. operators = new RegExp('^\\b(' + operators.join('|') + ')\\b')
  136. wordOperators = new RegExp('^\\b(' + wordOperators.join('|') + ')\\b')
  137. // We have to return "null" instead of null, in order to avoid string
  138. // styling as the default, when using Django templates inside HTML
  139. // element attributes
  140. function tokenBase(stream, state) {
  141. // Attempt to identify a variable, template or comment tag respectively
  142. if (stream.match('{{')) {
  143. state.tokenize = inVariable
  144. return 'tag'
  145. } else if (stream.match('{%')) {
  146. state.tokenize = inTag
  147. return 'tag'
  148. } else if (stream.match('{#')) {
  149. state.tokenize = inComment
  150. return 'comment'
  151. }
  152. // Ignore completely any stream series that do not match the
  153. // Django template opening tags.
  154. while (stream.next() != null && !stream.match(/\{[{%#]/, false)) {}
  155. return null
  156. }
  157. // A string can be included in either single or double quotes (this is
  158. // the delimiter). Mark everything as a string until the start delimiter
  159. // occurs again.
  160. function inString(delimiter, previousTokenizer) {
  161. return function (stream, state) {
  162. if (!state.escapeNext && stream.eat(delimiter)) {
  163. state.tokenize = previousTokenizer
  164. } else {
  165. if (state.escapeNext) {
  166. state.escapeNext = false
  167. }
  168. var ch = stream.next()
  169. // Take into account the backslash for escaping characters, such as
  170. // the string delimiter.
  171. if (ch == '\\') {
  172. state.escapeNext = true
  173. }
  174. }
  175. return 'string'
  176. }
  177. }
  178. // Apply Django template variable syntax highlighting
  179. function inVariable(stream, state) {
  180. // Attempt to match a dot that precedes a property
  181. if (state.waitDot) {
  182. state.waitDot = false
  183. if (stream.peek() != '.') {
  184. return 'null'
  185. }
  186. // Dot followed by a non-word character should be considered an error.
  187. if (stream.match(/\.\W+/)) {
  188. return 'error'
  189. } else if (stream.eat('.')) {
  190. state.waitProperty = true
  191. return 'null'
  192. } else {
  193. throw Error('Unexpected error while waiting for property.')
  194. }
  195. }
  196. // Attempt to match a pipe that precedes a filter
  197. if (state.waitPipe) {
  198. state.waitPipe = false
  199. if (stream.peek() != '|') {
  200. return 'null'
  201. }
  202. // Pipe followed by a non-word character should be considered an error.
  203. if (stream.match(/\.\W+/)) {
  204. return 'error'
  205. } else if (stream.eat('|')) {
  206. state.waitFilter = true
  207. return 'null'
  208. } else {
  209. throw Error('Unexpected error while waiting for filter.')
  210. }
  211. }
  212. // Highlight properties
  213. if (state.waitProperty) {
  214. state.waitProperty = false
  215. if (stream.match(/\b(\w+)\b/)) {
  216. state.waitDot = true // A property can be followed by another property
  217. state.waitPipe = true // A property can be followed by a filter
  218. return 'property'
  219. }
  220. }
  221. // Highlight filters
  222. if (state.waitFilter) {
  223. state.waitFilter = false
  224. if (stream.match(filters)) {
  225. return 'variable-2'
  226. }
  227. }
  228. // Ignore all white spaces
  229. if (stream.eatSpace()) {
  230. state.waitProperty = false
  231. return 'null'
  232. }
  233. // Identify numbers
  234. if (stream.match(/\b\d+(\.\d+)?\b/)) {
  235. return 'number'
  236. }
  237. // Identify strings
  238. if (stream.match("'")) {
  239. state.tokenize = inString("'", state.tokenize)
  240. return 'string'
  241. } else if (stream.match('"')) {
  242. state.tokenize = inString('"', state.tokenize)
  243. return 'string'
  244. }
  245. // Attempt to find the variable
  246. if (stream.match(/\b(\w+)\b/) && !state.foundVariable) {
  247. state.waitDot = true
  248. state.waitPipe = true // A property can be followed by a filter
  249. return 'variable'
  250. }
  251. // If found closing tag reset
  252. if (stream.match('}}')) {
  253. state.waitProperty = null
  254. state.waitFilter = null
  255. state.waitDot = null
  256. state.waitPipe = null
  257. state.tokenize = tokenBase
  258. return 'tag'
  259. }
  260. // If nothing was found, advance to the next character
  261. stream.next()
  262. return 'null'
  263. }
  264. function inTag(stream, state) {
  265. // Attempt to match a dot that precedes a property
  266. if (state.waitDot) {
  267. state.waitDot = false
  268. if (stream.peek() != '.') {
  269. return 'null'
  270. }
  271. // Dot followed by a non-word character should be considered an error.
  272. if (stream.match(/\.\W+/)) {
  273. return 'error'
  274. } else if (stream.eat('.')) {
  275. state.waitProperty = true
  276. return 'null'
  277. } else {
  278. throw Error('Unexpected error while waiting for property.')
  279. }
  280. }
  281. // Attempt to match a pipe that precedes a filter
  282. if (state.waitPipe) {
  283. state.waitPipe = false
  284. if (stream.peek() != '|') {
  285. return 'null'
  286. }
  287. // Pipe followed by a non-word character should be considered an error.
  288. if (stream.match(/\.\W+/)) {
  289. return 'error'
  290. } else if (stream.eat('|')) {
  291. state.waitFilter = true
  292. return 'null'
  293. } else {
  294. throw Error('Unexpected error while waiting for filter.')
  295. }
  296. }
  297. // Highlight properties
  298. if (state.waitProperty) {
  299. state.waitProperty = false
  300. if (stream.match(/\b(\w+)\b/)) {
  301. state.waitDot = true // A property can be followed by another property
  302. state.waitPipe = true // A property can be followed by a filter
  303. return 'property'
  304. }
  305. }
  306. // Highlight filters
  307. if (state.waitFilter) {
  308. state.waitFilter = false
  309. if (stream.match(filters)) {
  310. return 'variable-2'
  311. }
  312. }
  313. // Ignore all white spaces
  314. if (stream.eatSpace()) {
  315. state.waitProperty = false
  316. return 'null'
  317. }
  318. // Identify numbers
  319. if (stream.match(/\b\d+(\.\d+)?\b/)) {
  320. return 'number'
  321. }
  322. // Identify strings
  323. if (stream.match("'")) {
  324. state.tokenize = inString("'", state.tokenize)
  325. return 'string'
  326. } else if (stream.match('"')) {
  327. state.tokenize = inString('"', state.tokenize)
  328. return 'string'
  329. }
  330. // Attempt to match an operator
  331. if (stream.match(operators)) {
  332. return 'operator'
  333. }
  334. // Attempt to match a word operator
  335. if (stream.match(wordOperators)) {
  336. return 'keyword'
  337. }
  338. // Attempt to match a keyword
  339. var keywordMatch = stream.match(keywords)
  340. if (keywordMatch) {
  341. if (keywordMatch[0] == 'comment') {
  342. state.blockCommentTag = true
  343. }
  344. return 'keyword'
  345. }
  346. // Attempt to match a variable
  347. if (stream.match(/\b(\w+)\b/)) {
  348. state.waitDot = true
  349. state.waitPipe = true // A property can be followed by a filter
  350. return 'variable'
  351. }
  352. // If found closing tag reset
  353. if (stream.match('%}')) {
  354. state.waitProperty = null
  355. state.waitFilter = null
  356. state.waitDot = null
  357. state.waitPipe = null
  358. // If the tag that closes is a block comment tag, we want to mark the
  359. // following code as comment, until the tag closes.
  360. if (state.blockCommentTag) {
  361. state.blockCommentTag = false // Release the "lock"
  362. state.tokenize = inBlockComment
  363. } else {
  364. state.tokenize = tokenBase
  365. }
  366. return 'tag'
  367. }
  368. // If nothing was found, advance to the next character
  369. stream.next()
  370. return 'null'
  371. }
  372. // Mark everything as comment inside the tag and the tag itself.
  373. function inComment(stream, state) {
  374. if (stream.match(/^.*?#\}/)) state.tokenize = tokenBase
  375. else stream.skipToEnd()
  376. return 'comment'
  377. }
  378. // Mark everything as a comment until the `blockcomment` tag closes.
  379. function inBlockComment(stream, state) {
  380. if (stream.match(/\{%\s*endcomment\s*%\}/, false)) {
  381. state.tokenize = inTag
  382. stream.match('{%')
  383. return 'tag'
  384. } else {
  385. stream.next()
  386. return 'comment'
  387. }
  388. }
  389. return {
  390. startState: function () {
  391. return { tokenize: tokenBase }
  392. },
  393. token: function (stream, state) {
  394. return state.tokenize(stream, state)
  395. },
  396. blockCommentStart: '{% comment %}',
  397. blockCommentEnd: '{% endcomment %}',
  398. }
  399. })
  400. CodeMirror.defineMode('django', function (config) {
  401. var htmlBase = CodeMirror.getMode(config, 'text/html')
  402. var djangoInner = CodeMirror.getMode(config, 'django:inner')
  403. return CodeMirror.overlayMode(htmlBase, djangoInner)
  404. })
  405. CodeMirror.defineMIME('text/x-django', 'django')
  406. })