complete.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. (function () {
  2. // Minimal event-handling wrapper.
  3. function stopEvent() {
  4. if (this.preventDefault) {this.preventDefault(); this.stopPropagation();}
  5. else {this.returnValue = false; this.cancelBubble = true;}
  6. }
  7. function addStop(event) {
  8. if (!event.stop) event.stop = stopEvent;
  9. return event;
  10. }
  11. function connect(node, type, handler) {
  12. function wrapHandler(event) {handler(addStop(event || window.event));}
  13. if (typeof node.addEventListener == "function")
  14. node.addEventListener(type, wrapHandler, false);
  15. else
  16. node.attachEvent("on" + type, wrapHandler);
  17. }
  18. function forEach(arr, f) {
  19. for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
  20. }
  21. var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
  22. lineNumbers: true,
  23. theme: "night",
  24. onKeyEvent: function(i, e) {
  25. // Hook into ctrl-space
  26. if (e.keyCode == 32 && (e.ctrlKey || e.metaKey) && !e.altKey) {
  27. e.stop();
  28. return startComplete();
  29. }
  30. }
  31. });
  32. function startComplete() {
  33. // We want a single cursor position.
  34. if (editor.somethingSelected()) return;
  35. // Find the token at the cursor
  36. var cur = editor.getCursor(false), token = editor.getTokenAt(cur), tprop = token;
  37. // If it's not a 'word-style' token, ignore the token.
  38. if (!/^[\w$_]*$/.test(token.string)) {
  39. token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state,
  40. className: token.string == "." ? "js-property" : null};
  41. }
  42. // If it is a property, find out what it is a property of.
  43. while (tprop.className == "js-property") {
  44. tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
  45. if (tprop.string != ".") return;
  46. tprop = editor.getTokenAt({line: cur.line, ch: tprop.start});
  47. if (!context) var context = [];
  48. context.push(tprop);
  49. }
  50. var completions = getCompletions(token, context);
  51. if (!completions.length) return;
  52. function insert(str) {
  53. editor.replaceRange(str, {line: cur.line, ch: token.start}, {line: cur.line, ch: token.end});
  54. }
  55. // When there is only one completion, use it directly.
  56. if (completions.length == 1) {insert(completions[0]); return true;}
  57. // Build the select widget
  58. var complete = document.createElement("div");
  59. complete.className = "completions";
  60. var sel = complete.appendChild(document.createElement("select"));
  61. sel.multiple = true;
  62. for (var i = 0; i < completions.length; ++i) {
  63. var opt = sel.appendChild(document.createElement("option"));
  64. opt.appendChild(document.createTextNode(completions[i]));
  65. }
  66. sel.firstChild.selected = true;
  67. sel.size = Math.min(10, completions.length);
  68. var pos = editor.cursorCoords();
  69. complete.style.left = pos.x + "px";
  70. complete.style.top = pos.yBot + "px";
  71. document.body.appendChild(complete);
  72. // Hack to hide the scrollbar.
  73. if (completions.length <= 10)
  74. complete.style.width = (sel.clientWidth - 1) + "px";
  75. var done = false;
  76. function close() {
  77. if (done) return;
  78. done = true;
  79. complete.parentNode.removeChild(complete);
  80. }
  81. function pick() {
  82. insert(sel.options[sel.selectedIndex].value);
  83. close();
  84. setTimeout(function(){editor.focus();}, 50);
  85. }
  86. connect(sel, "blur", close);
  87. connect(sel, "keydown", function(event) {
  88. var code = event.keyCode;
  89. // Enter and space
  90. if (code == 13 || code == 32) {event.stop(); pick();}
  91. // Escape
  92. else if (code == 27) {event.stop(); close(); editor.focus();}
  93. else if (code != 38 && code != 40) {close(); editor.focus(); setTimeout(startComplete, 50);}
  94. });
  95. connect(sel, "dblclick", pick);
  96. sel.focus();
  97. // Opera sometimes ignores focusing a freshly created node
  98. if (window.opera) setTimeout(function(){if (!done) sel.focus();}, 100);
  99. return true;
  100. }
  101. var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
  102. "toUpperCase toLowerCase split concat match replace search").split(" ");
  103. var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
  104. "lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
  105. var funcProps = "prototype apply call bind".split(" ");
  106. var keywords = ("break case catch continue debugger default delete do else false finally for function " +
  107. "if in instanceof new null return switch throw true try typeof var void while with").split(" ");
  108. function getCompletions(token, context) {
  109. var found = [], start = token.string;
  110. function maybeAdd(str) {
  111. if (str.indexOf(start) == 0) found.push(str);
  112. }
  113. function gatherCompletions(obj) {
  114. if (typeof obj == "string") forEach(stringProps, maybeAdd);
  115. else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
  116. else if (obj instanceof Function) forEach(funcProps, maybeAdd);
  117. for (var name in obj) maybeAdd(name);
  118. }
  119. if (context) {
  120. // If this is a property, see if it belongs to some object we can
  121. // find in the current environment.
  122. var obj = context.pop(), base;
  123. if (obj.className == "js-variable")
  124. base = window[obj.string];
  125. else if (obj.className == "js-string")
  126. base = "";
  127. else if (obj.className == "js-atom")
  128. base = 1;
  129. while (base != null && context.length)
  130. base = base[context.pop().string];
  131. if (base != null) gatherCompletions(base);
  132. }
  133. else {
  134. // If not, just look in the window object and any local scope
  135. // (reading into JS mode internals to get at the local variables)
  136. for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
  137. gatherCompletions(window);
  138. forEach(keywords, maybeAdd);
  139. }
  140. return found;
  141. }
  142. })();