From 18cf334054d21d092b85bedc7d76f536d44ca6ec Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 28 Nov 2008 11:19:29 +0000 Subject: [PATCH] Wait for before evaluating funcitons in JS completion. --- content/completion.js | 76 +++++++++++++++++++++++++++++++------------ content/io.js | 12 ++++++- content/style.js | 1 + content/ui.js | 19 +++++++++-- 4 files changed, 84 insertions(+), 24 deletions(-) diff --git a/content/completion.js b/content/completion.js index 5b8a4203..965d416f 100644 --- a/content/completion.js +++ b/content/completion.js @@ -54,14 +54,14 @@ function CompletionContext(editor, name, offset) self.parent = parent; self.offset = parent.offset + (offset || 0); self.keys = util.cloneObject(parent.keys); - delete self._generate; delete self._filter; // FIXME? + delete self._generate; delete self._ignoreCase; ["anchored", "compare", "editor", "filterFunc", "keys", "_process", "quote", "title", "top"].forEach(function (key) self[key] = parent[key]); if (self != this) return self; - ["_caret", "contextList", "onUpdate", "selectionTypes", "tabPressed", "updateAsync", "value"].forEach(function (key) { + ["_caret", "contextList", "onUpdate", "selectionTypes", "tabPressed", "updateAsync", "value", "waitingForTab"].forEach(function (key) { self.__defineGetter__(key, function () this.top[key]); self.__defineSetter__(key, function (val) this.top[key] = val); }); @@ -84,9 +84,9 @@ function CompletionContext(editor, name, offset) let text = Array.concat(this.getKey(item, "text")); for (let [i, str] in Iterator(text)) { - if (this.match(str)) + if (this.match(String(str))) { - item.text = text[i]; + item.text = String(text[i]); return true; } } @@ -100,10 +100,11 @@ function CompletionContext(editor, name, offset) this.__defineGetter__("incomplete", function () this.contextList.some(function (c) c.parent && c.incomplete)); this.reset(); } - this.name = name || ""; this.cache = {}; - this.key = ""; this.itemCache = {}; + this.key = ""; + this.message = null; + this.name = name || ""; this._completions = []; // FIXME this.getKey = function (item, key) (typeof self.keys[key] == "function") ? self.keys[key].call(this, item) : item.item[self.keys[key]]; } @@ -111,15 +112,23 @@ CompletionContext.prototype = { // Temporary get allItems() { - let self = this; - let minStart = Math.min.apply(Math, [context.offset for ([k, context] in Iterator(this.contexts)) if (context.items.length && context.hasItems)]); - let items = this.contextList.map(function (context) { - if (!context.hasItems) - return []; - let prefix = self.value.substring(minStart, context.offset); - return context.items.map(function makeItem(item) ({ text: prefix + item.text, item: item.item })); - }); - return { start: minStart, items: util.Array.flatten(items), longestSubstring: this.longestAllSubstring } + try + { + let self = this; + let minStart = Math.min.apply(Math, [context.offset for ([k, context] in Iterator(this.contexts)) if (context.items.length && context.hasItems)]); + let items = this.contextList.map(function (context) { + if (!context.hasItems) + return []; + let prefix = self.value.substring(minStart, context.offset); + return context.items.map(function makeItem(item) ({ text: prefix + item.text, item: item.item })); + }); + return { start: minStart, items: util.Array.flatten(items), longestSubstring: this.longestAllSubstring } + } + catch (e) + { + liberator.reportError(e); + return { start: 0, items: [], longestAllSubstring: "" } + } }, // Temporary get allSubstrings() @@ -135,6 +144,8 @@ CompletionContext.prototype = { function (res, list) res.filter( function (str) list.some(function (s) s.substr(0, str.length) == str)), lists.pop()); + if (!substrings) // FIXME: How is this undefined? + return []; return util.Array.uniq(substrings); }, // Temporary @@ -429,6 +440,7 @@ CompletionContext.prototype = { this.selectionTypes = {}; this.tabPressed = false; this.title = ["Completions"]; + this.waitingForTab = false; this.updateAsync = false; if (this.editor) { @@ -473,6 +485,7 @@ function Completion() //{{{ .createInstance(Components.interfaces.nsIJSON); const OFFSET = 0, CHAR = 1, STATEMENTS = 2, DOTS = 3, FULL_STATEMENTS = 4, FUNCTIONS = 5; let stack = []; + let functions = []; let top = []; /* The element on the top of the stack. */ let last = ""; /* The last opening char pushed onto the stack. */ let lastNonwhite = ""; /* Last non-whitespace character we saw. */ @@ -564,8 +577,6 @@ function Completion() //{{{ this.eval = function eval(arg, key, tmp) { - if (!this.context.cache.eval) - this.context.cache.eval = {}; let cache = this.context.cache.eval; if (!key) key = arg; @@ -629,6 +640,7 @@ function Completion() //{{{ let i = 0, c = ""; /* Current index and character, respectively. */ stack = []; + functions = []; push("#root"); /* Build a parse stack, discarding entries as opening characters @@ -669,6 +681,7 @@ function Completion() //{{{ /* Function call, or if/while/for/... */ if (/[\w\d$]/.test(lastNonwhite)) { + functions.push(i); top[FUNCTIONS].push(i); top[STATEMENTS].pop(); } @@ -723,6 +736,9 @@ function Completion() //{{{ return; } + if (!this.context.cache.eval) + this.context.cache.eval = {}; + /* Okay, have parse stack. Figure out what we're completing. */ if (/[\])}"';]/.test(str[lastIdx - 1]) && last != '"' && last != '"') @@ -733,8 +749,21 @@ function Completion() //{{{ let prev = 0; for (let [,v] in Iterator(get(0)[FULL_STATEMENTS])) { - this.eval(str.substring(prev, v + 1)); - prev = v + 1; + let key = str.substring(prev, v + 1); + if (checkFunction(prev, v, key)) + return; + this.eval(key); + prev = v + 1; + } + + function checkFunction(start, end, key) + { + let res = functions.some(function (idx) idx >= start && idx < end); + if (!res || self.context.tabPressed || key in self.context.cache.eval) + return false; + self.context.waitingForTab = true; + self.context.message = "Waiting for "; + return true; } // For each DOT in a statement, prefix it with TMP, eval it, @@ -757,10 +786,15 @@ function Completion() //{{{ if (dot > stop) break; let s = str.substring(prev, dot); + if (prev != statement) s = EVAL_TMP + "." + s; - prev = dot + 1; cacheKey = str.substring(statement, dot); + + if (checkFunction(prev, dot, cacheKey)) + return []; + + prev = dot + 1; obj = self.eval(s, cacheKey, obj); } return [[obj, cacheKey]] @@ -889,6 +923,8 @@ function Completion() //{{{ let [offset, obj, func] = getObjKey(-3); let key = str.substring(get(-2, 0, STATEMENTS), top[OFFSET]) + "''"; + if (!obj.length) + return; try { diff --git a/content/io.js b/content/io.js index 504ec0fb..fd80444b 100644 --- a/content/io.js +++ b/content/io.js @@ -27,6 +27,16 @@ the provisions above, a recipient may use your version of this file under the terms of any one of the MPL, the GPL or the LGPL. }}} ***** END LICENSE BLOCK *****/ +plugins.contexts = {}; +function Script(name) +{ + if (plugins.contexts[name]) + return plugins.contexts[name]; + plugins.contexts[name] = this; + this.NAME = name; +} +Script.prototype = plugins; + // TODO: why are we passing around strings rather than file objects? function IO() //{{{ { @@ -815,7 +825,7 @@ lookup: .getService(Components.interfaces.mozIJSSubScriptLoader); try { - loader.loadSubScript(uri.spec, {__proto__: plugins}); + loader.loadSubScript(uri.spec, new Script(file.path)); } catch (e) { diff --git a/content/style.js b/content/style.js index 3cec64b2..7c23d5f5 100644 --- a/content/style.js +++ b/content/style.js @@ -28,6 +28,7 @@ function Highlights(name, store, serial) CompTitle color: magenta; background: white; font-weight: bold; CompTitle>* border-bottom: 1px dashed magenta; + CompMsg font-style: italic; margin-left: 16px; CompItem CompItem[selected] background: yellow; CompItem>* padding: 0 .5ex; diff --git a/content/ui.js b/content/ui.js index 6adf75a1..7006dcec 100644 --- a/content/ui.js +++ b/content/ui.js @@ -137,7 +137,7 @@ function CommandLine() //{{{ let full = !longest && wildmode.checkHas(wildType, "full"); // we need to build our completion list first - if (completionIndex == UNINITIALIZED) + if (completionIndex == UNINITIALIZED || completionContext.waitingForTab) { completionIndex = -1; completionPrefix = command.substring(0, commandWidget.selectionStart); @@ -1365,13 +1365,14 @@ function ItemList(id) //{{{ items.contextList.forEach(function init_eachContext(context) { delete context.cache.nodes; - if (!context.items.length) + if (!context.items.length && !context.message) return; context.cache.nodes = []; dom(
{context.createRow(context.title || [], "CompTitle")}
+
@@ -1396,6 +1397,7 @@ function ItemList(id) //{{{ startIndex = offset; endIndex = Math.min(startIndex + maxItems, items.allItems.items.length); + let haveCompletions = false; let off = 0; function getRows(context) { @@ -1410,9 +1412,20 @@ function ItemList(id) //{{{ let nodes = context.cache.nodes; if (!nodes) return; + haveCompletions = true; + + nodes.message.style.display = "none"; + if (context.message) + { + nodes.message.textContent = context.message; + nodes.message.style.display = "block"; + } let root = nodes.root let items = nodes.items; let [start, end] = getRows(context); + if (start == end) + return; + for (let [i, row] in Iterator(context.getRows(start, end, doc))) nodes[i] = row; for (let [i, row] in util.Array.iterator2(nodes)) @@ -1434,7 +1447,7 @@ function ItemList(id) //{{{ nodes.down.style.display = (end == context.items.length) ? "none" : "block"; }); - divNodes.noCompletions.style.display = (off > 0) ? "none" : "block"; + divNodes.noCompletions.style.display = haveCompletions ? "none" : "block"; completionElements = buffer.evaluateXPath("//xhtml:div[@liberator:highlight='CompItem']", doc);