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.

340 lines
12 KiB

  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: http://codemirror.net/LICENSE
  3. (function(mod) {
  4. if (typeof exports == "object" && typeof module == "object") // CommonJS
  5. mod(require("../../lib/codemirror"));
  6. else if (typeof define == "function" && define.amd) // AMD
  7. define(["../../lib/codemirror"], mod);
  8. else // Plain browser env
  9. mod(CodeMirror);
  10. })(function(CodeMirror) {
  11. "use strict";
  12. function wordRegexp(words) {
  13. return new RegExp("^((" + words.join(")|(") + "))\\b");
  14. }
  15. var wordOperators = wordRegexp(["and", "or", "not", "is"]);
  16. var commonKeywords = ["as", "assert", "break", "class", "continue",
  17. "def", "del", "elif", "else", "except", "finally",
  18. "for", "from", "global", "if", "import",
  19. "lambda", "pass", "raise", "return",
  20. "try", "while", "with", "yield", "in"];
  21. var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
  22. "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
  23. "enumerate", "eval", "filter", "float", "format", "frozenset",
  24. "getattr", "globals", "hasattr", "hash", "help", "hex", "id",
  25. "input", "int", "isinstance", "issubclass", "iter", "len",
  26. "list", "locals", "map", "max", "memoryview", "min", "next",
  27. "object", "oct", "open", "ord", "pow", "property", "range",
  28. "repr", "reversed", "round", "set", "setattr", "slice",
  29. "sorted", "staticmethod", "str", "sum", "super", "tuple",
  30. "type", "vars", "zip", "__import__", "NotImplemented",
  31. "Ellipsis", "__debug__"];
  32. CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins));
  33. function top(state) {
  34. return state.scopes[state.scopes.length - 1];
  35. }
  36. CodeMirror.defineMode("python", function(conf, parserConf) {
  37. var ERRORCLASS = "error";
  38. var singleDelimiters = parserConf.singleDelimiters || /^[\(\)\[\]\{\}@,:`=;\.]/;
  39. var doubleOperators = parserConf.doubleOperators || /^([!<>]==|<>|<<|>>|\/\/|\*\*)/;
  40. var doubleDelimiters = parserConf.doubleDelimiters || /^(\+=|\-=|\*=|%=|\/=|&=|\|=|\^=)/;
  41. var tripleDelimiters = parserConf.tripleDelimiters || /^(\/\/=|>>=|<<=|\*\*=)/;
  42. var hangingIndent = parserConf.hangingIndent || conf.indentUnit;
  43. var myKeywords = commonKeywords, myBuiltins = commonBuiltins;
  44. if (parserConf.extra_keywords != undefined)
  45. myKeywords = myKeywords.concat(parserConf.extra_keywords);
  46. if (parserConf.extra_builtins != undefined)
  47. myBuiltins = myBuiltins.concat(parserConf.extra_builtins);
  48. var py3 = parserConf.version && parseInt(parserConf.version, 10) == 3
  49. if (py3) {
  50. // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
  51. var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/;
  52. var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
  53. myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]);
  54. myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]);
  55. var stringPrefixes = new RegExp("^(([rbuf]|(br))?('{3}|\"{3}|['\"]))", "i");
  56. } else {
  57. var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!]/;
  58. var identifiers = parserConf.identifiers|| /^[_A-Za-z][_A-Za-z0-9]*/;
  59. myKeywords = myKeywords.concat(["exec", "print"]);
  60. myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
  61. "file", "intern", "long", "raw_input", "reduce", "reload",
  62. "unichr", "unicode", "xrange", "False", "True", "None"]);
  63. var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
  64. }
  65. var keywords = wordRegexp(myKeywords);
  66. var builtins = wordRegexp(myBuiltins);
  67. // tokenizers
  68. function tokenBase(stream, state) {
  69. if (stream.sol()) state.indent = stream.indentation()
  70. // Handle scope changes
  71. if (stream.sol() && top(state).type == "py") {
  72. var scopeOffset = top(state).offset;
  73. if (stream.eatSpace()) {
  74. var lineOffset = stream.indentation();
  75. if (lineOffset > scopeOffset)
  76. pushPyScope(state);
  77. else if (lineOffset < scopeOffset && dedent(stream, state))
  78. state.errorToken = true;
  79. return null;
  80. } else {
  81. var style = tokenBaseInner(stream, state);
  82. if (scopeOffset > 0 && dedent(stream, state))
  83. style += " " + ERRORCLASS;
  84. return style;
  85. }
  86. }
  87. return tokenBaseInner(stream, state);
  88. }
  89. function tokenBaseInner(stream, state) {
  90. if (stream.eatSpace()) return null;
  91. var ch = stream.peek();
  92. // Handle Comments
  93. if (ch == "#") {
  94. stream.skipToEnd();
  95. return "comment";
  96. }
  97. // Handle Number Literals
  98. if (stream.match(/^[0-9\.]/, false)) {
  99. var floatLiteral = false;
  100. // Floats
  101. if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; }
  102. if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; }
  103. if (stream.match(/^\.\d+/)) { floatLiteral = true; }
  104. if (floatLiteral) {
  105. // Float literals may be "imaginary"
  106. stream.eat(/J/i);
  107. return "number";
  108. }
  109. // Integers
  110. var intLiteral = false;
  111. // Hex
  112. if (stream.match(/^0x[0-9a-f]+/i)) intLiteral = true;
  113. // Binary
  114. if (stream.match(/^0b[01]+/i)) intLiteral = true;
  115. // Octal
  116. if (stream.match(/^0o[0-7]+/i)) intLiteral = true;
  117. // Decimal
  118. if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) {
  119. // Decimal literals may be "imaginary"
  120. stream.eat(/J/i);
  121. // TODO - Can you have imaginary longs?
  122. intLiteral = true;
  123. }
  124. // Zero by itself with no other piece of number.
  125. if (stream.match(/^0(?![\dx])/i)) intLiteral = true;
  126. if (intLiteral) {
  127. // Integer literals may be "long"
  128. stream.eat(/L/i);
  129. return "number";
  130. }
  131. }
  132. // Handle Strings
  133. if (stream.match(stringPrefixes)) {
  134. state.tokenize = tokenStringFactory(stream.current());
  135. return state.tokenize(stream, state);
  136. }
  137. // Handle operators and Delimiters
  138. if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters))
  139. return "punctuation";
  140. if (stream.match(doubleOperators) || stream.match(singleOperators))
  141. return "operator";
  142. if (stream.match(singleDelimiters))
  143. return "punctuation";
  144. if (state.lastToken == "." && stream.match(identifiers))
  145. return "property";
  146. if (stream.match(keywords) || stream.match(wordOperators))
  147. return "keyword";
  148. if (stream.match(builtins))
  149. return "builtin";
  150. if (stream.match(/^(self|cls)\b/))
  151. return "variable-2";
  152. if (stream.match(identifiers)) {
  153. if (state.lastToken == "def" || state.lastToken == "class")
  154. return "def";
  155. return "variable";
  156. }
  157. // Handle non-detected items
  158. stream.next();
  159. return ERRORCLASS;
  160. }
  161. function tokenStringFactory(delimiter) {
  162. while ("rub".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)
  163. delimiter = delimiter.substr(1);
  164. var singleline = delimiter.length == 1;
  165. var OUTCLASS = "string";
  166. function tokenString(stream, state) {
  167. while (!stream.eol()) {
  168. stream.eatWhile(/[^'"\\]/);
  169. if (stream.eat("\\")) {
  170. stream.next();
  171. if (singleline && stream.eol())
  172. return OUTCLASS;
  173. } else if (stream.match(delimiter)) {
  174. state.tokenize = tokenBase;
  175. return OUTCLASS;
  176. } else {
  177. stream.eat(/['"]/);
  178. }
  179. }
  180. if (singleline) {
  181. if (parserConf.singleLineStringErrors)
  182. return ERRORCLASS;
  183. else
  184. state.tokenize = tokenBase;
  185. }
  186. return OUTCLASS;
  187. }
  188. tokenString.isString = true;
  189. return tokenString;
  190. }
  191. function pushPyScope(state) {
  192. while (top(state).type != "py") state.scopes.pop()
  193. state.scopes.push({offset: top(state).offset + conf.indentUnit,
  194. type: "py",
  195. align: null})
  196. }
  197. function pushBracketScope(stream, state, type) {
  198. var align = stream.match(/^([\s\[\{\(]|#.*)*$/, false) ? null : stream.column() + 1
  199. state.scopes.push({offset: state.indent + hangingIndent,
  200. type: type,
  201. align: align})
  202. }
  203. function dedent(stream, state) {
  204. var indented = stream.indentation();
  205. while (state.scopes.length > 1 && top(state).offset > indented) {
  206. if (top(state).type != "py") return true;
  207. state.scopes.pop();
  208. }
  209. return top(state).offset != indented;
  210. }
  211. function tokenLexer(stream, state) {
  212. if (stream.sol()) state.beginningOfLine = true;
  213. var style = state.tokenize(stream, state);
  214. var current = stream.current();
  215. // Handle decorators
  216. if (state.beginningOfLine && current == "@")
  217. return stream.match(identifiers, false) ? "meta" : py3 ? "operator" : ERRORCLASS;
  218. if (/\S/.test(current)) state.beginningOfLine = false;
  219. if ((style == "variable" || style == "builtin")
  220. && state.lastToken == "meta")
  221. style = "meta";
  222. // Handle scope changes.
  223. if (current == "pass" || current == "return")
  224. state.dedent += 1;
  225. if (current == "lambda") state.lambda = true;
  226. if (current == ":" && !state.lambda && top(state).type == "py")
  227. pushPyScope(state);
  228. var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1;
  229. if (delimiter_index != -1)
  230. pushBracketScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1));
  231. delimiter_index = "])}".indexOf(current);
  232. if (delimiter_index != -1) {
  233. if (top(state).type == current) state.indent = state.scopes.pop().offset - hangingIndent
  234. else return ERRORCLASS;
  235. }
  236. if (state.dedent > 0 && stream.eol() && top(state).type == "py") {
  237. if (state.scopes.length > 1) state.scopes.pop();
  238. state.dedent -= 1;
  239. }
  240. return style;
  241. }
  242. var external = {
  243. startState: function(basecolumn) {
  244. return {
  245. tokenize: tokenBase,
  246. scopes: [{offset: basecolumn || 0, type: "py", align: null}],
  247. indent: basecolumn || 0,
  248. lastToken: null,
  249. lambda: false,
  250. dedent: 0
  251. };
  252. },
  253. token: function(stream, state) {
  254. var addErr = state.errorToken;
  255. if (addErr) state.errorToken = false;
  256. var style = tokenLexer(stream, state);
  257. if (style && style != "comment")
  258. state.lastToken = (style == "keyword" || style == "punctuation") ? stream.current() : style;
  259. if (style == "punctuation") style = null;
  260. if (stream.eol() && state.lambda)
  261. state.lambda = false;
  262. return addErr ? style + " " + ERRORCLASS : style;
  263. },
  264. indent: function(state, textAfter) {
  265. if (state.tokenize != tokenBase)
  266. return state.tokenize.isString ? CodeMirror.Pass : 0;
  267. var scope = top(state), closing = scope.type == textAfter.charAt(0)
  268. if (scope.align != null)
  269. return scope.align - (closing ? 1 : 0)
  270. else
  271. return scope.offset - (closing ? hangingIndent : 0)
  272. },
  273. electricInput: /^\s*[\}\]\)]$/,
  274. closeBrackets: {triples: "'\""},
  275. lineComment: "#",
  276. fold: "indent"
  277. };
  278. return external;
  279. });
  280. CodeMirror.defineMIME("text/x-python", "python");
  281. var words = function(str) { return str.split(" "); };
  282. CodeMirror.defineMIME("text/x-cython", {
  283. name: "python",
  284. extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+
  285. "extern gil include nogil property public"+
  286. "readonly struct union DEF IF ELIF ELSE")
  287. });
  288. });