You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
12 KiB

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