PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/common/modules/javascript.jsm

https://bitbucket.org/oshybystyi/pentadactyl-fork
Unknown | 902 lines | 758 code | 144 blank | 0 comment | 0 complexity | 6bedbeb98c40461798f0d7410bbff360 MD5 | raw file
  1. // Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
  2. //
  3. // This work is licensed for reuse under an MIT license. Details are
  4. // given in the LICENSE.txt file included with this file.
  5. /* use strict */
  6. let { getOwnPropertyNames } = Object;
  7. try {
  8. defineModule("javascript", {
  9. exports: ["JavaScript", "javascript"],
  10. require: ["util"]
  11. });
  12. lazyRequire("template", ["template"]);
  13. let isPrototypeOf = Object.prototype.isPrototypeOf;
  14. // TODO: Clean this up.
  15. var JavaScript = Module("javascript", {
  16. init: function () {
  17. this._stack = [];
  18. this._functions = [];
  19. this._top = {}; // The element on the top of the stack.
  20. this._last = ""; // The last opening char pushed onto the stack.
  21. this._lastNonwhite = ""; // Last non-whitespace character we saw.
  22. this._lastChar = ""; // Last character we saw, used for \ escaping quotes.
  23. this._str = "";
  24. this._lastIdx = 0;
  25. this._cacheKey = null;
  26. this._nullSandbox = Cu.Sandbox("about:blank");
  27. },
  28. Local: function (dactyl, modules, window) ({
  29. init: function init() {
  30. this.modules = modules;
  31. this.window = window;
  32. init.supercall(this);
  33. }
  34. }),
  35. globals: Class.Memoize(function () [
  36. [this.modules.userContext, /*L*/"Global Variables"],
  37. [this.modules, "modules"],
  38. [this.window, "window"]
  39. ]),
  40. toplevel: Class.Memoize(function () this.modules.jsmodules),
  41. lazyInit: true,
  42. newContext: function () this.modules.newContext(this.modules.userContext, true, "Dactyl JS Temp Context"),
  43. completers: Class.Memoize(function () Object.create(JavaScript.completers)),
  44. // Some object members are only accessible as function calls
  45. getKey: function (obj, key) {
  46. try {
  47. return obj[key];
  48. }
  49. catch (e) {}
  50. return undefined;
  51. },
  52. iter: function iter_(obj, toplevel) {
  53. if (obj == null)
  54. return;
  55. let seen = isinstance(obj, ["Sandbox"]) ? Set(JavaScript.magicalNames) : {};
  56. let globals = values(toplevel && this.window === obj ? this.globalNames : []);
  57. if (toplevel && isObject(obj) && "wrappedJSObject" in obj)
  58. if (!Set.add(seen, "wrappedJSObject"))
  59. yield "wrappedJSObject";
  60. for (let key in iter(globals, properties(obj, !toplevel, true)))
  61. if (!Set.add(seen, key))
  62. yield key;
  63. // Properties aren't visible in an XPCNativeWrapper until
  64. // they're accessed.
  65. for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel, true))
  66. try {
  67. if (key in obj && !Set.has(seen, key))
  68. yield key;
  69. }
  70. catch (e) {}
  71. },
  72. objectKeys: function objectKeys(obj, toplevel) {
  73. // Things we can dereference
  74. if (!obj || ["object", "string", "function"].indexOf(typeof obj) === -1)
  75. return [];
  76. if (isinstance(obj, ["Sandbox"]) && !toplevel) // Temporary hack.
  77. return [];
  78. if (isPrototypeOf.call(this.toplevel, obj) && !toplevel)
  79. return [];
  80. let completions = [k for (k in this.iter(obj, toplevel))];
  81. if (obj === this.modules) // Hack.
  82. completions = array.uniq(completions.concat([k for (k in this.iter(this.modules.jsmodules, toplevel))]));
  83. return completions;
  84. },
  85. evalled: function evalled(arg, key, tmp) {
  86. let cache = this.context.cache.evalled;
  87. let context = this.context.cache.evalContext;
  88. if (!key)
  89. key = arg;
  90. if (key in cache)
  91. return cache[key];
  92. context[JavaScript.EVAL_TMP] = tmp;
  93. try {
  94. cache[key] = this.modules.dactyl.userEval(arg, context, /*L*/"[Command Line Completion]", 1);
  95. return cache[key];
  96. }
  97. catch (e) {
  98. util.reportError(e);
  99. this.context.message = _("error.error", e);
  100. return null;
  101. }
  102. finally {
  103. delete context[JavaScript.EVAL_TMP];
  104. }
  105. },
  106. // Get an element from the stack. If @frame is negative,
  107. // count from the top of the stack, otherwise, the bottom.
  108. // If @nth is provided, return the @mth value of element @type
  109. // of the stack entry at @frame.
  110. _get: function (frame, nth, type) {
  111. let a = this._stack[frame >= 0 ? frame : this._stack.length + frame];
  112. if (type != null)
  113. a = a[type];
  114. if (nth == null)
  115. return a;
  116. return a[a.length - nth - 1];
  117. },
  118. // Push and pop the stack, maintaining references to 'top' and 'last'.
  119. _push: function push(arg) {
  120. this._top = {
  121. offset: this._i,
  122. char: arg,
  123. statements: [this._i],
  124. dots: [],
  125. fullStatements: [],
  126. comma: [],
  127. functions: []
  128. };
  129. this._last = this._top.char;
  130. this._stack.push(this._top);
  131. },
  132. _pop: function pop(arg) {
  133. if (this._i == this.context.caret - 1)
  134. this.context.highlight(this._top.offset, 1, "FIND");
  135. if (this._top.char != arg) {
  136. this.context.highlight(this._top.offset, this._i - this._top.offset, "SPELLCHECK");
  137. throw Error(/*L*/"Invalid JS");
  138. }
  139. // The closing character of this stack frame will have pushed a new
  140. // statement, leaving us with an empty statement. This doesn't matter,
  141. // now, as we simply throw away the frame when we pop it, but it may later.
  142. if (this._top.statements[this._top.statements.length - 1] == this._i)
  143. this._top.statements.pop();
  144. this._top = this._get(-2);
  145. this._last = this._top.char;
  146. return this._stack.pop();
  147. },
  148. _buildStack: function (filter) {
  149. // Todo: Fix these one-letter variable names.
  150. this._i = 0;
  151. this._c = ""; // Current index and character, respectively.
  152. // Reuse the old stack.
  153. if (this._str && filter.substr(0, this._str.length) == this._str) {
  154. this.context.highlight(0, 0, "FIND");
  155. this._i = this._str.length;
  156. if (this.popStatement)
  157. this._top.statements.pop();
  158. }
  159. else {
  160. this.context.highlight();
  161. this._stack = [];
  162. this._functions = [];
  163. this._push("#root");
  164. }
  165. // Build a parse stack, discarding entries as opening characters
  166. // match closing characters. The stack is walked from the top entry
  167. // and down as many levels as it takes us to figure out what it is
  168. // that we're completing.
  169. this._str = filter;
  170. let length = this._str.length;
  171. for (; this._i < length; this._lastChar = this._c, this._i++) {
  172. this._c = this._str[this._i];
  173. if (/['"\/]/.test(this._last)) {
  174. if (this._lastChar == "\\") { // Escape. Skip the next char, whatever it may be.
  175. this._c = "";
  176. this._i++;
  177. }
  178. else if (this._c == this._last)
  179. this._pop(this._c);
  180. }
  181. else {
  182. // A word character following a non-word character, or simply a non-word
  183. // character. Start a new statement.
  184. if (/[a-zA-Z_$]/.test(this._c) && !/[\w$]/.test(this._lastChar) || !/[\w\s$]/.test(this._c))
  185. this._top.statements.push(this._i);
  186. // A "." or a "[" dereferences the last "statement" and effectively
  187. // joins it to this logical statement.
  188. if ((this._c == "." || this._c == "[") && /[\w$\])"']/.test(this._lastNonwhite)
  189. || this._lastNonwhite == "." && /[a-zA-Z_$]/.test(this._c))
  190. this._top.statements.pop();
  191. switch (this._c) {
  192. case "(":
  193. // Function call, or if/while/for/...
  194. if (/[\w$]/.test(this._lastNonwhite)) {
  195. this._functions.push(this._i);
  196. this._top.functions.push(this._i);
  197. this._top.statements.pop();
  198. }
  199. case '"':
  200. case "'":
  201. case "/":
  202. case "{":
  203. case "[":
  204. this._push(this._c);
  205. break;
  206. case ".":
  207. this._top.dots.push(this._i);
  208. break;
  209. case ")": this._pop("("); break;
  210. case "]": this._pop("["); break;
  211. case "}": this._pop("{"); // Fallthrough
  212. case ";":
  213. this._top.fullStatements.push(this._i);
  214. break;
  215. case ",":
  216. this._top.comma.push(this._i);
  217. break;
  218. }
  219. if (/\S/.test(this._c))
  220. this._lastNonwhite = this._c;
  221. }
  222. }
  223. this.popStatement = false;
  224. if (!/[\w$]/.test(this._lastChar) && this._lastNonwhite != ".") {
  225. this.popStatement = true;
  226. this._top.statements.push(this._i);
  227. }
  228. this._lastIdx = this._i;
  229. },
  230. // Don't eval any function calls unless the user presses tab.
  231. _checkFunction: function (start, end, key) {
  232. let res = this._functions.some(function (idx) idx >= start && idx < end);
  233. if (!res || this.context.tabPressed || key in this.cache.evalled)
  234. return false;
  235. this.context.waitingForTab = true;
  236. return true;
  237. },
  238. // For each DOT in a statement, prefix it with TMP, eval it,
  239. // and save the result back to TMP. The point of this is to
  240. // cache the entire path through an object chain, mainly in
  241. // the presence of function calls. There are drawbacks. For
  242. // instance, if the value of a variable changes in the course
  243. // of inputting a command (let foo=bar; frob(foo); foo=foo.bar; ...),
  244. // we'll still use the old value. But, it's worth it.
  245. _getObj: function (frame, stop) {
  246. let statement = this._get(frame, 0, "statements") || 0; // Current statement.
  247. let prev = statement;
  248. let obj = this.window;
  249. let cacheKey;
  250. for (let [, dot] in Iterator(this._get(frame).dots.concat(stop))) {
  251. if (dot < statement)
  252. continue;
  253. if (dot > stop || dot <= prev)
  254. break;
  255. let s = this._str.substring(prev, dot);
  256. if (prev != statement)
  257. s = JavaScript.EVAL_TMP + "." + s;
  258. cacheKey = this._str.substring(statement, dot);
  259. if (this._checkFunction(prev, dot, cacheKey))
  260. return [];
  261. if (prev != statement && obj == null) {
  262. this.context.message = /*L*/"Error: " + cacheKey.quote() + " is " + String(obj);
  263. return [];
  264. }
  265. prev = dot + 1;
  266. obj = this.evalled(s, cacheKey, obj);
  267. }
  268. return [[obj, cacheKey]];
  269. },
  270. _getObjKey: function (frame) {
  271. let dot = this._get(frame, 0, "dots") || -1; // Last dot in frame.
  272. let statement = this._get(frame, 0, "statements") || 0; // Current statement.
  273. let end = (frame == -1 ? this._lastIdx : this._get(frame + 1).offset);
  274. this._cacheKey = null;
  275. let obj = [[this.cache.evalContext, /*L*/"Local Variables"]].concat(this.globals);
  276. // Is this an object dereference?
  277. if (dot < statement) // No.
  278. dot = statement - 1;
  279. else // Yes. Set the object to the string before the dot.
  280. obj = this._getObj(frame, dot);
  281. let [, space, key] = this._str.substring(dot + 1, end).match(/^(\s*)(.*)/);
  282. return [dot + 1 + space.length, obj, key];
  283. },
  284. _complete: function (objects, key, compl, string, last) {
  285. const self = this;
  286. if (!getOwnPropertyNames && !services.debugger.isOn && !this.context.message)
  287. this.context.message = /*L*/"For better completion data, please enable the JavaScript debugger (:set jsdebugger)";
  288. let base = this.context.fork("js", this._top.offset);
  289. base.forceAnchored = true;
  290. base.filter = last == null ? key : string;
  291. let prefix = last != null ? key : "";
  292. if (last == null) // We're not looking for a quoted string, so filter out anything that's not a valid identifier
  293. base.filters.push(function (item) /^[a-zA-Z_$][\w$]*$/.test(item.text));
  294. else {
  295. base.quote = [last, function (text) util.escapeString(text, ""), last];
  296. if (prefix)
  297. base.filters.push(function (item) item.item.indexOf(prefix) === 0);
  298. }
  299. if (!compl) {
  300. base.process[1] = function highlight(item, v)
  301. template.highlight(typeof v == "xml" ? new String(v.toXMLString()) : v, true, 200);
  302. // Sort in a logical fashion for object keys:
  303. // Numbers are sorted as numbers, rather than strings, and appear first.
  304. // Constants are unsorted, and appear before other non-null strings.
  305. // Other strings are sorted in the default manner.
  306. let isnan = function isnan(item) item != '' && isNaN(item);
  307. let compare = base.compare;
  308. base.compare = function (a, b) {
  309. if (!isnan(a.key) && !isnan(b.key))
  310. return a.key - b.key;
  311. return isnan(b.key) - isnan(a.key) || compare(a, b);
  312. };
  313. base.keys = {
  314. text: prefix ? function (text) text.substr(prefix.length) : util.identity,
  315. description: function (item) self.getKey(this.obj, item),
  316. key: function (item) {
  317. if (!isNaN(key))
  318. return parseInt(key);
  319. if (/^[A-Z_][A-Z0-9_]*$/.test(key))
  320. return "";
  321. return item;
  322. }
  323. };
  324. }
  325. // We've already listed anchored matches, so don't list them again here.
  326. function unanchored(item) util.compareIgnoreCase(item.text.substr(0, this.filter.length), this.filter);
  327. objects.forEach(function (obj) {
  328. let context = base.fork(obj[1]);
  329. context.title = [obj[1]];
  330. context.keys.obj = function () obj[0];
  331. context.key = obj[1] + last;
  332. if (obj[0] == this.cache.evalContext)
  333. context.regenerate = true;
  334. obj.ctxt_t = context.fork("toplevel");
  335. if (!compl) {
  336. obj.ctxt_p = context.fork("prototypes");
  337. obj.ctxt_t.generate = function () self.objectKeys(obj[0], true);
  338. obj.ctxt_p.generate = function () self.objectKeys(obj[0], false);
  339. }
  340. }, this);
  341. // TODO: Make this a generic completion helper function.
  342. objects.forEach(function (obj) {
  343. obj.ctxt_t.split(obj[1] + "/anchored", this, function (context) {
  344. context.anchored = true;
  345. if (compl)
  346. compl(context, obj[0]);
  347. });
  348. });
  349. if (compl)
  350. return;
  351. objects.forEach(function (obj) {
  352. obj.ctxt_p.split(obj[1] + "/anchored", this, function (context) {
  353. context.anchored = true;
  354. context.title[0] += /*L*/" (prototypes)";
  355. });
  356. });
  357. objects.forEach(function (obj) {
  358. obj.ctxt_t.split(obj[1] + "/unanchored", this, function (context) {
  359. context.anchored = false;
  360. context.title[0] += /*L*/" (substrings)";
  361. context.filters.push(unanchored);
  362. });
  363. });
  364. objects.forEach(function (obj) {
  365. obj.ctxt_p.split(obj[1] + "/unanchored", this, function (context) {
  366. context.anchored = false;
  367. context.title[0] += /*L*/" (prototype substrings)";
  368. context.filters.push(unanchored);
  369. });
  370. });
  371. },
  372. _getKey: function () {
  373. if (this._last == "")
  374. return "";
  375. // After the opening [ upto the opening ", plus '' to take care of any operators before it
  376. let key = this._str.substring(this._get(-2, null, "offset") + 1, this._get(-1, null, "offset")) + "''";
  377. // Now eval the key, to process any referenced variables.
  378. return this.evalled(key);
  379. },
  380. get cache() this.context.cache,
  381. complete: function _complete(context) {
  382. const self = this;
  383. this.context = context;
  384. try {
  385. this._buildStack.call(this, context.filter);
  386. }
  387. catch (e) {
  388. this._lastIdx = 0;
  389. util.assert(!e.message, e.message);
  390. return null;
  391. }
  392. this.context.getCache("evalled", Object);
  393. this.context.getCache("evalContext", this.closure.newContext);
  394. // Okay, have parse stack. Figure out what we're completing.
  395. // Find any complete statements that we can eval before we eval our object.
  396. // This allows for things like:
  397. // let doc = content.document; let elem = doc.createEle<Tab> ...
  398. let prev = 0;
  399. for (let [, v] in Iterator(this._get(0).fullStatements)) {
  400. let key = this._str.substring(prev, v + 1);
  401. if (this._checkFunction(prev, v, key))
  402. return null;
  403. this.evalled(key);
  404. prev = v + 1;
  405. }
  406. // If this is a function argument, try to get the function's
  407. // prototype and show it.
  408. try {
  409. let i = (this._get(-2) && this._get(-2).char == "(") ? -2 : -1;
  410. if (this._get(i).char == "(") {
  411. let [offset, obj, funcName] = this._getObjKey(i - 1);
  412. if (obj.length) {
  413. let func = obj[0][0][funcName];
  414. if (callable(func)) {
  415. let [, prefix, args] = /^(function .*?)\((.*?)\)/.exec(Function.prototype.toString.call(func));
  416. let n = this._get(i).comma.length;
  417. args = template.map(Iterator(args.split(", ")),
  418. function ([i, arg]) <span highlight={i == n ? "Filter" : ""}>{arg}</span>,
  419. <>,&#xa0;</>);
  420. this.context.message = <>{prefix}({args})</>;
  421. }
  422. }
  423. }
  424. }
  425. catch (e) {}
  426. // In a string. Check if we're dereferencing an object or
  427. // completing a function argument. Otherwise, do nothing.
  428. if (this._last == "'" || this._last == '"') {
  429. // str = "foo[bar + 'baz"
  430. // obj = "foo"
  431. // key = "bar + ''"
  432. // The top of the stack is the sting we're completing.
  433. // Wrap it in its delimiters and eval it to process escape sequences.
  434. let string = this._str.substring(this._get(-1).offset + 1, this._lastIdx).replace(/((?:\\\\)*)\\/, "$1");
  435. string = Cu.evalInSandbox(this._last + string + this._last, this._nullSandbox);
  436. // Is this an object accessor?
  437. if (this._get(-2).char == "[") { // Are we inside of []?
  438. // Stack:
  439. // [-1]: "...
  440. // [-2]: [...
  441. // [-3]: base statement
  442. // Yes. If the [ starts at the beginning of a logical
  443. // statement, we're in an array literal, and we're done.
  444. if (this._get(-3, 0, "statements") == this._get(-2).offset)
  445. return null;
  446. // Beginning of the statement upto the opening [
  447. let obj = this._getObj(-3, this._get(-2).offset);
  448. return this._complete(obj, this._getKey(), null, string, this._last);
  449. }
  450. // Is this a function call?
  451. if (this._get(-2).char == "(") {
  452. // Stack:
  453. // [-1]: "...
  454. // [-2]: (...
  455. // [-3]: base statement
  456. // Does the opening "(" mark a function call?
  457. if (this._get(-3, 0, "functions") != this._get(-2).offset)
  458. return null; // No. We're done.
  459. let [offset, obj, funcName] = this._getObjKey(-3);
  460. if (!obj.length)
  461. return null;
  462. obj = obj.slice(0, 1);
  463. try {
  464. let func = obj[0][0][funcName];
  465. var completer = func.dactylCompleter;
  466. }
  467. catch (e) {}
  468. if (!completer)
  469. completer = this.completers[funcName];
  470. if (!completer)
  471. return null;
  472. // Split up the arguments
  473. let prev = this._get(-2).offset;
  474. let args = [];
  475. for (let [i, idx] in Iterator(this._get(-2).comma)) {
  476. let arg = this._str.substring(prev + 1, idx);
  477. prev = idx;
  478. memoize(args, i, function () self.evalled(arg));
  479. }
  480. let key = this._getKey();
  481. args.push(key + string);
  482. let compl = function (context, obj) {
  483. let res = completer.call(self, context, funcName, obj, args);
  484. if (res)
  485. context.completions = res;
  486. };
  487. obj[0][1] += "." + funcName + "(... [" + args.length + "]";
  488. return this._complete(obj, key, compl, string, this._last);
  489. }
  490. // In a string that's not an obj key or a function arg.
  491. // Nothing to do.
  492. return null;
  493. }
  494. // str = "foo.bar.baz"
  495. // obj = "foo.bar"
  496. // key = "baz"
  497. //
  498. // str = "foo"
  499. // obj = [modules, window]
  500. // key = "foo"
  501. let [offset, obj, key] = this._getObjKey(-1);
  502. // Wait for a keypress before completing when there's no key
  503. if (!this.context.tabPressed && key == "" && obj.length > 1) {
  504. let message = this.context.message || "";
  505. this.context.waitingForTab = true;
  506. this.context.message = <>{message}
  507. {_("completion.waitingForKeyPress")}</>;
  508. return null;
  509. }
  510. if (!/^(?:[a-zA-Z_$][\w$]*)?$/.test(key))
  511. return null; // Not a word. Forget it. Can this even happen?
  512. try { // FIXME
  513. var o = this._top.offset;
  514. this._top.offset = offset;
  515. return this._complete(obj, key);
  516. }
  517. finally {
  518. this._top.offset = o;
  519. }
  520. return null;
  521. },
  522. magicalNames: Class.Memoize(function () Object.getOwnPropertyNames(Cu.Sandbox(this.window), true).sort()),
  523. /**
  524. * A list of properties of the global object which are not
  525. * enumerable by any standard method.
  526. */
  527. globalNames: Class.Memoize(function () let (self = this) array.uniq([
  528. "Array", "ArrayBuffer", "AttributeName", "Audio", "Boolean", "Components",
  529. "CSSFontFaceStyleDecl", "CSSGroupRuleRuleList", "CSSNameSpaceRule",
  530. "CSSRGBColor", "CSSRect", "ComputedCSSStyleDeclaration", "Date", "Error",
  531. "EvalError", "File", "Float32Array", "Float64Array", "Function",
  532. "HTMLDelElement", "HTMLInsElement", "HTMLSpanElement", "Infinity",
  533. "InnerModalContentWindow", "InnerWindow", "Int16Array", "Int32Array",
  534. "Int8Array", "InternalError", "Iterator", "JSON", "KeyboardEvent",
  535. "Math", "NaN", "Namespace", "Number", "Object", "Proxy", "QName",
  536. "ROCSSPrimitiveValue", "RangeError", "ReferenceError", "RegExp",
  537. "StopIteration", "String", "SyntaxError", "TypeError", "URIError",
  538. "Uint16Array", "Uint32Array", "Uint8Array", "XML", "XMLHttpProgressEvent",
  539. "XMLList", "XMLSerializer", "XPCNativeWrapper", "XPCSafeJSWrapper",
  540. "XULControllers", "constructor", "decodeURI", "decodeURIComponent",
  541. "encodeURI", "encodeURIComponent", "escape", "eval", "isFinite", "isNaN",
  542. "isXMLName", "parseFloat", "parseInt", "undefined", "unescape", "uneval"
  543. ].concat([k.substr(6) for (k in keys(Ci)) if (/^nsIDOM/.test(k))])
  544. .concat([k.substr(3) for (k in keys(Ci)) if (/^nsI/.test(k))])
  545. .concat(this.magicalNames)
  546. .filter(function (k) k in self.window))),
  547. }, {
  548. EVAL_TMP: "__dactyl_eval_tmp",
  549. /**
  550. * A map of argument completion functions for named methods. The
  551. * signature and specification of the completion function
  552. * are fairly complex and yet undocumented.
  553. *
  554. * @see JavaScript.setCompleter
  555. */
  556. completers: {},
  557. /**
  558. * Installs argument string completers for a set of functions.
  559. * The second argument is an array of functions (or null
  560. * values), each corresponding the argument of the same index.
  561. * Each provided completion function receives as arguments a
  562. * CompletionContext, the 'this' object of the method, and an
  563. * array of values for the preceding arguments.
  564. *
  565. * It is important to note that values in the arguments array
  566. * provided to the completers are lazily evaluated the first
  567. * time they are accessed, so they should be accessed
  568. * judiciously.
  569. *
  570. * @param {function|[function]} funcs The functions for which to
  571. * install the completers.
  572. * @param {[function]} completers An array of completer
  573. * functions.
  574. */
  575. setCompleter: function (funcs, completers) {
  576. funcs = Array.concat(funcs);
  577. for (let [, func] in Iterator(funcs)) {
  578. func.dactylCompleter = function (context, func, obj, args) {
  579. let completer = completers[args.length - 1];
  580. if (!completer)
  581. return [];
  582. return completer.call(obj, context, obj, args);
  583. };
  584. }
  585. return arguments[0];
  586. }
  587. }, {
  588. init: function init(dactyl, modules, window) {
  589. init.superapply(this, arguments);
  590. modules.JavaScript = Class("JavaScript", JavaScript, { modules: modules, window: window });
  591. },
  592. completion: function (dactyl, modules, window) {
  593. const { completion } = modules;
  594. update(modules.completion, {
  595. get javascript() modules.javascript.closure.complete,
  596. javascriptCompleter: JavaScript // Backwards compatibility
  597. });
  598. },
  599. modes: function initModes(dactyl, modules, window) {
  600. initModes.require("commandline");
  601. const { modes } = modules;
  602. modes.addMode("REPL", {
  603. description: "JavaScript Read Eval Print Loop",
  604. bases: [modes.COMMAND_LINE],
  605. displayName: Class.Memoize(function () this.name)
  606. });
  607. },
  608. commandline: function initCommandLine(dactyl, modules, window) {
  609. const { Buffer, modes } = modules;
  610. var REPL = Class("REPL", {
  611. init: function init(context) {
  612. this.context = context;
  613. this.results = [];
  614. },
  615. addOutput: function addOutput(js) {
  616. default xml namespace = XHTML;
  617. this.count++;
  618. try {
  619. var result = dactyl.userEval(js, this.context);
  620. var xml = result === undefined ? "" : util.objectToString(result, true);
  621. }
  622. catch (e) {
  623. util.reportError(e);
  624. result = e;
  625. if (e.fileName)
  626. e = util.fixURI(e.fileName) + ":" + e.lineNumber + ": " + e;
  627. xml = <span highlight="ErrorMsg">{e}</span>;
  628. }
  629. let prompt = "js" + this.count;
  630. Class.replaceProperty(this.context, prompt, result);
  631. XML.ignoreWhitespace = XML.prettyPrinting = false;
  632. let nodes = {};
  633. this.rootNode.appendChild(
  634. util.xmlToDom(<e4x>
  635. <div highlight="REPL-E" key="e"><span highlight="REPL-R">{prompt}></span> {js}</div>
  636. <div highlight="REPL-P" key="p">{xml}</div>
  637. </e4x>.elements(), this.document, nodes));
  638. this.rootNode.scrollTop += nodes.e.getBoundingClientRect().top
  639. - this.rootNode.getBoundingClientRect().top;
  640. },
  641. count: 0,
  642. message: Class.Memoize(function () {
  643. default xml namespace = XHTML;
  644. util.xmlToDom(<div highlight="REPL" key="rootNode"/>,
  645. this.document, this);
  646. return this.rootNode;
  647. }),
  648. __noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.rootNode].concat(args))
  649. });
  650. modules.CommandREPLMode = Class("CommandREPLMode", modules.CommandMode, {
  651. init: function init(context) {
  652. init.supercall(this);
  653. let self = this;
  654. let sandbox = true || isinstance(context, ["Sandbox"]);
  655. this.context = modules.newContext(context, !sandbox, "Dactyl REPL Context");
  656. this.js = modules.JavaScript();
  657. this.js.replContext = this.context;
  658. this.js.newContext = function newContext() modules.newContext(self.context, !sandbox, "Dactyl REPL Temp Context");
  659. this.js.globals = [
  660. [this.context, /*L*/"REPL Variables"],
  661. [context, /*L*/"REPL Global"]
  662. ].concat(this.js.globals.filter(function ([global]) isPrototypeOf.call(global, context)));
  663. if (!isPrototypeOf.call(modules.jsmodules, context))
  664. this.js.toplevel = context;
  665. if (!isPrototypeOf.call(window, context))
  666. this.js.window = context;
  667. if (this.js.globals.slice(2).some(function ([global]) global === context))
  668. this.js.globals.splice(1);
  669. this.repl = REPL(this.context);
  670. },
  671. open: function open(context) {
  672. modules.mow.echo(this.repl);
  673. this.widgets.message = null;
  674. open.superapply(this, arguments);
  675. this.updatePrompt();
  676. },
  677. complete: function complete(context) {
  678. context.fork("js", 0, this.js, "complete");
  679. },
  680. historyKey: "javascript",
  681. mode: modes.REPL,
  682. get completionList() this.widgets.statusbar.commandline.id,
  683. accept: function accept() {
  684. dactyl.trapErrors(function () { this.repl.addOutput(this.command); }, this);
  685. this.completions.cleanup();
  686. this.history.save();
  687. this.history.reset();
  688. this.updatePrompt();
  689. modules.mow.resize();
  690. },
  691. leave: function leave(params) {
  692. leave.superapply(this, arguments);
  693. if (!params.push)
  694. modes.delay(function () { modes.pop(); });
  695. },
  696. updatePrompt: function updatePrompt() {
  697. this.command = "";
  698. this.prompt = ["REPL-R", "js" + (this.repl.count + 1) + "> "];
  699. }
  700. });
  701. },
  702. commands: function initCommands(dactyl, modules, window) {
  703. const { commands } = modules;
  704. commands.add(["javas[cript]", "js"],
  705. "Evaluate a JavaScript string",
  706. function (args) {
  707. if (args[0] && !args.bang)
  708. dactyl.userEval(args[0]);
  709. else {
  710. modules.commandline;
  711. modules.CommandREPLMode(args[0] ? dactyl.userEval(args[0]) : modules.userContext)
  712. .open();
  713. }
  714. }, {
  715. argCount: "?",
  716. bang: true,
  717. completer: function (context) modules.completion.javascript(context),
  718. hereDoc: true,
  719. literal: 0
  720. });
  721. },
  722. mappings: function initMappings(dactyl, modules, window) {
  723. const { mappings, modes } = modules;
  724. function bind() mappings.add.apply(mappings,
  725. [[modes.REPL]].concat(Array.slice(arguments)))
  726. bind(["<Return>"], "Accept the current input",
  727. function ({ self }) { self.accept(); });
  728. bind(["<C-e>"], "Scroll down one line",
  729. function ({ self }) { self.repl.scrollVertical("lines", 1); });
  730. bind(["<C-y>"], "Scroll up one line",
  731. function ({ self }) { self.repl.scrollVertical("lines", -1); });
  732. bind(["<C-d>"], "Scroll down half a page",
  733. function ({ self }) { self.repl.scrollVertical("pages", .5); });
  734. bind(["<C-f>", "<PageDown>"], "Scroll down one page",
  735. function ({ self }) { self.repl.scrollVertical("pages", 1); });
  736. bind(["<C-u>"], "Scroll up half a page",
  737. function ({ self }) { self.repl.scrollVertical("pages", -.5); });
  738. bind(["<C-b>", "<PageUp>"], "Scroll up half a page",
  739. function ({ self }) { self.repl.scrollVertical("pages", -1); });
  740. },
  741. options: function (dactyl, modules, window) {
  742. modules.options.add(["jsdebugger", "jsd"],
  743. "Enable the JavaScript debugger service for use in JavaScript completion",
  744. "boolean", false, {
  745. setter: function (value) {
  746. if (services.debugger.isOn != value)
  747. if (value)
  748. (services.debugger.asyncOn || services.debugger.on)(null);
  749. else
  750. services.debugger.off();
  751. },
  752. getter: function () services.debugger.isOn
  753. });
  754. }
  755. });
  756. endModule();
  757. } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
  758. // vim: set fdm=marker sw=4 ts=4 et ft=javascript: