diff --git a/content/bookmarks.js b/content/bookmarks.js index 7d1cc2c5..0334eca4 100644 --- a/content/bookmarks.js +++ b/content/bookmarks.js @@ -723,6 +723,8 @@ function History() //{{{ function (args) { history.list(args.string, args.bang); }, { bang: true, + literal: true, + completer: function (context) completion.history(context) // completer: function (filter) completion.history(filter) }); diff --git a/content/commands.js b/content/commands.js index f5198318..5839d8cd 100644 --- a/content/commands.js +++ b/content/commands.js @@ -602,7 +602,7 @@ function Commands() //{{{ if (complete) { - if (argCount == "0" || args.length > 0 && (argCount == "1" || argCount == "?")) + if (argCount == "0" || args.length > 0 && (/[1?]/.test(argCount))) complete.highlight(i, sub.length, "SPELLCHECK") } @@ -639,9 +639,9 @@ function Commands() //{{{ if (complete) args.completeArg = args.length - 1; - if (count <= 0) - break; i += count; + if (count <= 0 || i == str.length) + break; } if (complete) @@ -666,12 +666,14 @@ function Commands() //{{{ } // check for correct number of arguments - if (!complete && (args.length == 0 && /^[1+]$/.test(argCount) || - // TODO: what is this for? -- djk - literal && argCount == "+" && /^\s*$/.test(args.literalArg))) + if (args.length == 0 && /^[1+]$/.test(argCount) || + literal && argCount == "+" && /^\s*$/.test(args.literalArg)) { - liberator.echoerr("E471: Argument required"); - return null; + if (!complete) + { + liberator.echoerr("E471: Argument required"); + return null; + } } else if (args.length == 1 && (argCount == "0") || args.length > 1 && /^[01?]$/.test(argCount)) diff --git a/content/completion.js b/content/completion.js index 75b0a581..2998d86e 100644 --- a/content/completion.js +++ b/content/completion.js @@ -117,7 +117,6 @@ CompletionContext.prototype = { return []; let prefix = self.value.substring(minStart, context.offset); return context.items.map(function makeItem(item) ({ text: prefix + item.text, item: item.item })); - //return [{ text: prefix + item.text, item: item.item } for ([i, item] in Iterator(context.items))]; }); return { start: minStart, items: util.Array.flatten(items), longestSubstring: this.longestAllSubstring } }, @@ -157,13 +156,16 @@ CompletionContext.prototype = { get completions() this._completions || [], set completions(items) { + // Accept a generator + if (!(items instanceof Array)) + items = [x for (x in items)]; delete this.cache.filtered; delete this.cache.filter; this.cache.rows = []; this.hasItems = items.length > 0; this._completions = items; let self = this; - if (this.updateAsync) + if (this.updateAsync && !this.noUpdate) liberator.callInMainThread(function () { self.onUpdate.call(self) }); }, @@ -234,10 +236,9 @@ CompletionContext.prototype = { if (this.generate && !this.background) { // XXX - let updateAsync = this.updateAsync; - this.updateAsync = false; + this.noUpdate = true; this.completions = items = this.generate(); - this.updateAsync = updateAsync; + this.noUpdate = false; } this.cache.filter = this.filter; if (items == null) @@ -320,6 +321,7 @@ CompletionContext.prototype = { this.offset += count; if (this.quote) { + this.offset += this.quote[0].length; this.quote[0] = ""; this.quote[2] = ""; } @@ -461,7 +463,6 @@ function Completion() //{{{ let str = ""; let lastIdx = 0; - let continuing = false; let cacheKey = null; @@ -545,12 +546,11 @@ function Completion() //{{{ this.eval = function eval(arg, key, tmp) { - if (!("eval" in this.context.cache)) + if (!this.context.cache.eval) this.context.cache.eval = {}; let cache = this.context.cache.eval; if (!key) key = arg; - if (key in cache) return cache[key]; @@ -608,23 +608,10 @@ function Completion() //{{{ return ret; } - let i = start, c = ""; /* Current index and character, respectively. */ + let i = 0, c = ""; /* Current index and character, respectively. */ - // We're starting afresh. - if (start == 0) - { - stack = []; - push("#root"); - } - else - { - // A new statement may have been pushed onto the stack just after - // the end of the last string. We'll throw it away for now, and - // add it again later if it turns out to be valid. - let s = top[STATEMENTS]; - if (s[s.length - 1] == start) - s.pop(); - } + stack = []; + push("#root"); /* Build a parse stack, discarding entries as opening characters * match closing characters. The stack is walked from the top entry @@ -704,20 +691,11 @@ function Completion() //{{{ this.context = context; let string = context.filter; - context.process = [null, function highlight(item, v) template.highlight(v, true)]; - context.compare = function ({item: {key: a}}, {item: {key: b}}) - { - if (!isNaN(a) && !isNaN(b)) - return a - b; - return String.localeCompare(a, b); - } - let self = this; try { - continuing = lastIdx && string.indexOf(str) == 0; str = string; - buildStack.call(this, continuing ? lastIdx : 0); + buildStack.call(this); } catch (e) { @@ -729,6 +707,9 @@ function Completion() //{{{ /* Okay, have parse stack. Figure out what we're completing. */ + if (/[\])}"';]/.test(str[lastIdx - 1]) && last != '"' && last != '"') + return; + // Find any complete statements that we can eval before we eval our object. // This allows for things like: let doc = window.content.document; let elem = doc.createElement...; elem. let prev = 0; @@ -788,35 +769,50 @@ function Completion() //{{{ function fill(context, obj, name, compl, anchored, key, last, offset) { context.title = [name]; - context.key = name; context.anchored = anchored; context.filter = key; context.itemCache = context.parent.itemCache; - if (compl) - context.completions = compl; - else - context.generate = function () self.objectKeys(obj); + context.key = name; if (last != undefined) context.quote = [last, function (text) util.escapeString(text.substr(offset), ""), last]; else // We're not looking for a quoted string, so filter out anything that's not a valid identifier context.filters.push(function (item) /^[\w$][\w\d$]*$/.test(item.text)); - if (!anchored) - context.filters.push(function (item) util.compareIgnoreCase(item.text.substr(0, key.length), key)); + + compl.call(self, context, obj); } function complete(objects, key, compl, string, last) { + let orig = compl; + if (!compl) + compl = function (context, obj) { + context.process = [null, function highlight(item, v) template.highlight(v, true)]; + if (!context.anchored) + context.filters.push(function (item) util.compareIgnoreCase(item.text.substr(0, key.length), key)); + context.compare = function ({item: {key: a}}, {item: {key: b}}) + { + if (!isNaN(a) && !isNaN(b)) + return a - b; + return String.localeCompare(a, b); + } + context.generate = function () self.objectKeys(obj); + } + let filter = key + (string || ""); for (let [,obj] in Iterator(objects)) { - this.context.fork(obj[1], top[OFFSET], this, fill, obj[0], obj[1], compl, - true, key + (string || ""), last, key.length); + try { + this.context.fork(obj[1], top[OFFSET], this, fill, + obj[0], obj[1], compl, compl != orig, filter, last, key.length); + } catch(e) { liberator.reportError(e) } } + if (orig) + return; for (let [,obj] in Iterator(objects)) { obj[1] += " (substrings)"; - this.context.fork(obj[1], top[OFFSET], this, fill, obj[0], obj[1], compl, - false, key + (string || ""), last, key.length); + this.context.fork(obj[1], top[OFFSET], this, fill, + obj[0], obj[1], compl, false, filter, last, key.length); } } @@ -888,19 +884,22 @@ function Completion() //{{{ // Split up the arguments let prev = get(-2)[OFFSET]; - let args = get(-2)[FULL_STATEMENTS].map(function splitArgs(s) + let args = []; + for (let [i, idx] in Iterator(get(-2)[FULL_STATEMENTS])) { - let ret = str.substring(prev + 1, s); - prev = s; - return ret; - }); - args.push(key); - - let compl = completer.call(this, func, obj[0][0], string, args); - if (!(compl instanceof Array)) - compl = [v for (v in compl)]; + let arg = str.substring(prev + 1, idx); + prev = idx; + args.__defineGetter__(i, function () self.eval(ret)); + } key = this.eval(key); - obj[0][1] += "." + func + "(..."; + args.push(key + string); + + compl = function (context, obj) { + let res = completer.call(self, context, func, obj, args); + if (res) + context.completions = res; + } + obj[0][1] += "." + func + "(... [" + args.length + "]"; return complete.call(this, obj, key, compl, string, last); } @@ -935,20 +934,16 @@ function Completion() //{{{ let self = { - // FIXME - get getKey() this._getKey || function (item, key) item[{ text: 0, description: 1, icon: 2 }[key]], - set getKey(getKey) this._getKey = getKey, - setFunctionCompleter: function setFunctionCompleter(funcs, completers) { funcs = Array.concat(funcs); for (let [,func] in Iterator(funcs)) { - func.liberatorCompleter = function liberatorCompleter(func, obj, string, args) { + func.liberatorCompleter = function liberatorCompleter(context, func, obj, args) { let completer = completers[args.length - 1]; if (!completer) return []; - return completer.call(this, this.eval(obj), this.eval(args.pop()) + string, args); + return completer.call(this, context, obj, args); }; } }, @@ -957,9 +952,7 @@ function Completion() //{{{ _runCompleter: function _runCompleter(name, filter) { let context = CompletionContext(filter); - if (typeof name == "string") - name = this[name]; - name.apply(this, [context].concat(Array.slice(arguments, 2))); + context.fork.apply(context, ["run", 0, this, name].concat(Array.slice(arguments, 2))); while (context.incomplete) liberator.threadYield(true, true); return context.allItems; @@ -1121,7 +1114,6 @@ function Completion() //{{{ text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url], url: url, indicator: indicator, - i: i, icon: tab.image }; }); diff --git a/content/io.js b/content/io.js index a5fcfb6c..f9324086 100644 --- a/content/io.js +++ b/content/io.js @@ -366,7 +366,10 @@ function IO() //{{{ liberator.registerObserver("load_completion", function () { completion.setFunctionCompleter([ioManager.getFile, ioManager.expandPath], - [function (obj, arg) completion.runCompleter("file", arg.replace(/[^\/]*$/, ""))]); + [function (context, obj, args) { + context.quote[2] = ""; + completion.file(context, true); + }]); }); var ioManager = { diff --git a/content/liberator.js b/content/liberator.js index 8e380205..05d18ca8 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -215,7 +215,7 @@ const liberator = (function () //{{{ { argCount: "1", bang: true, - completer: function (context) completion.dialog(context) + completer: function (context, args) completion.dialog(context) }); // TODO: move this diff --git a/content/mappings.js b/content/mappings.js index 3b848828..a43e8d39 100644 --- a/content/mappings.js +++ b/content/mappings.js @@ -263,9 +263,9 @@ function Mappings() //{{{ completion.setFunctionCompleter(mappings.get, [ null, - function (obj, filter, args) + function (context, obj, args) { - let mode = this.eval(args[0]); + let mode = args[0] return util.Array.flatten( [ [[name, map.description] for ([i, name] in Iterator(map.names))] diff --git a/content/options.js b/content/options.js index c0661ba8..ae1ca578 100644 --- a/content/options.js +++ b/content/options.js @@ -700,7 +700,7 @@ function Options() //{{{ context.title = ["Option"]; context.quote = [prefix, util.identity, ""]; context.keys = { text: "names", description: "description" }; - context.completions = [opt for (opt in opts)]; + context.completions = opts; return; } else if (prefix == "no")