diff --git a/content/buffer.js b/content/buffer.js index e32f2a4c..fe0c4d5d 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -513,7 +513,7 @@ function Buffer() //{{{ stylesheetSwitchAll(window.content, args); }, { - completer: function (context) completion.alternateStylesheet(context.filter), + completer: function (context) completion.alternateStylesheet(context), literal: true }); @@ -790,11 +790,9 @@ function Buffer() //{{{ { var stylesheets = getAllStyleSheets(window.content); - stylesheets = stylesheets.filter( + return stylesheets.filter( function (stylesheet) /^(screen|all|)$/i.test(stylesheet.media.mediaText) && !/^\s*$/.test(stylesheet.title) ); - - return stylesheets; }, get pageInfo() pageInfo, diff --git a/content/commands.js b/content/commands.js index 4244f0e3..ab40a968 100644 --- a/content/commands.js +++ b/content/commands.js @@ -852,7 +852,7 @@ function Commands() //{{{ { argCount: 2, bang: true, - completer: function (context) completion.userCommand(context.filter), + completer: function (context) completion.userCommand(context), options: [ [["-nargs"], commandManager.OPTION_STRING, function (arg) /^[01*?+]$/.test(arg), ["0", "1", "*", "?", "+"]], @@ -901,7 +901,7 @@ function Commands() //{{{ }, { argCount: "1", - completer: function (context) completion.userCommand(context.filter) + completer: function (context) completion.userCommand(context) }); //}}} diff --git a/content/completion.js b/content/completion.js index aa7f151e..13a07d95 100644 --- a/content/completion.js +++ b/content/completion.js @@ -46,23 +46,23 @@ function CompletionContext(editor, name, offset) name = parent.name + "/" + name; this.contexts = parent.contexts; if (name in this.contexts) - { self = this.contexts[name]; - self.offset = parent.offset + (offset || 0); - return self; - } - this.contexts[name] = this; - this.anchored = parent.anchored; - this.parent = parent; - this.offset = parent.offset + (offset || 0); - this.keys = util.cloneObject(this.parent.keys); + else + self.contexts[name] = this; + self.anchored = parent.anchored; + self.filters = parent.filters.slice(); + self.incomplete = false; + self.parent = parent; + self.offset = parent.offset + (offset || 0); + self.keys = util.cloneObject(parent.keys); ["compare", "editor", "filterFunc", "keys", "quote", "title", "top"].forEach(function (key) self[key] = parent[key]); ["contextList", "onUpdate", "selectionTypes", "tabPressed", "updateAsync", "value"].forEach(function (key) { self.__defineGetter__(key, function () this.top[key]); self.__defineSetter__(key, function (val) this.top[key] = val); }); - this.incomplete = false; + if (self != this) + return self; } else { @@ -71,7 +71,26 @@ function CompletionContext(editor, name, offset) else this.editor = editor; this.compare = function (a, b) String.localeCompare(a.text, b.text); - this.filterFunc = completion.filter; + this.filterFunc = function (items) + { + let self = this; + return this.filters.reduce(function (res, filter) + res.filter(function (item) filter.call(self, item)), + items); + } + this.filters = [function (item) { + let text = Array.concat(this.getKey(item, "text")); + let texts = this.ignoreCase ? text.map(String.toLowerCase) : text; + for (let [i, str] in Iterator(texts)) + { + if (this.match(str)) + { + item.text = text[i]; + return true; + } + } + return false; + }]; this.keys = { text: 0, description: 1, icon: "icon" }; this.offset = offset || 0; this.onUpdate = function () true; @@ -85,9 +104,11 @@ function CompletionContext(editor, name, offset) } this.name = name || ""; this.cache = {}; + this.key = ""; + this.itemCache = {}; this.process = []; this._completions = []; // FIXME - this.getKey = function (item, key) item.item[self.keys[key]]; + this.getKey = function (item, key) (typeof self.keys[key] == "function") ? self.keys[key].call(this, item) : item.item[self.keys[key]]; } CompletionContext.prototype = { // Temporary @@ -101,7 +122,30 @@ CompletionContext.prototype = { let prefix = self.value.substring(minStart, context.offset); return [{ text: prefix + item.text, item: item.item } for ([i, item] in Iterator(context.items))]; }); - return { start: minStart, items: util.Array.flatten(items) } + return { start: minStart, items: util.Array.flatten(items), longestSubstring: this.longestAllSubstring } + }, + get allSubstrings() + { + 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.substrings.map(function (str) prefix + str); + }); + return util.Array.uniq(util.Array.flatten(items), true); + }, + get longestAllSubstring() + { + let substrings = this.allSubstrings; + return substrings.reduce(function (res, str) + res.filter(function (s) { + let len = Math.min(s.length, str.length); + return str.substr(0, len) == s.substr(0, len) + }), + substrings) + .reduce(function (a, b) a.length > b.length ? a : b, ""); }, get caret() (this.editor ? this.editor.selection.getRangeAt(0).startOffset : this.value.length) - this.offset, @@ -125,19 +169,17 @@ CompletionContext.prototype = { get filterFunc() this._filterFunc || function (items) items, set filterFunc(val) this._filterFunc = val, - get regenerate() this._generate && (!this.completions || this.cache.key != this.key || this.cache.offset != this.offset), - set regenerate(val) { if (val) delete this.cache.offset }, + get regenerate() this._generate && (!this.completions || !this.itemCache[this.key] || this.cache.offset != this.offset), + set regenerate(val) { if (val) delete this.itemCache[this.key] }, get generate() !this._generate ? null : function () { - let updateAsync = this.updateAsync; // XXX - this.updateAsync = false; - this.completions = this._generate.call(this); - this.updateAsync = updateAsync; - + if (this.offset != this.cache.offset) + this.itemCache = {}; this.cache.offset = this.offset; - this.cache.key = this.key; - return this.completions; + if (!this.itemCache[this.key]) + this.itemCache[this.key] = this._generate.call(this); + return this.itemCache[this.key]; }, set generate(arg) { @@ -174,6 +216,9 @@ CompletionContext.prototype = { this.process = format.process || this.process; }, + // XXX + get ignoreCase() this.filter == this.filter.toLowerCase(), + get items() { if (!this.hasItems) @@ -182,18 +227,22 @@ CompletionContext.prototype = { return this.cache.filtered; this.cache.rows = []; let items = this.completions; - if (this.regenerate) - items = this.generate(); + if (this.generate) + { + // XXX + let updateAsync = this.updateAsync; + this.updateAsync = false; + this.completions = items = this.generate(); + this.updateAsync = updateAsync; + } this.cache.filter = this.filter; if (items == null) return items; let self = this; + delete this._substrings; - completion.getKey = this.getKey; // XXX - let filtered = this.filterFunc(items.map(function (item) ({ text: item[self.keys["text"]], item: item })), - this.filter, this.anchored); - completion.getKey = null; + let filtered = this.filterFunc(items.map(function (item) ({ text: item[self.keys["text"]], item: item }))); if (self.quote) filtered.forEach(function (item) item.text = self.quote(item.text)); @@ -218,6 +267,43 @@ CompletionContext.prototype = { this._process = process; }, + get substrings() + { + let items = this.items; + if (items.length == 0) + return []; + if (this._substrings) + return this._substrings; + + let fixCase = this.ignoreCase ? String.toLowerCase : function (str) str; + let text = fixCase(items[0].text); + let filter = fixCase(this.filter); + if (this.anchored) + { + function compare (text, s) text.substr(0, s.length) == s; + substrings = util.map(util.range(filter.length, text.length), + function (end) text.substring(0, end)); + } + else + { + function compare (text, s) text.indexOf(s) >= 0; + substrings = []; + let start = 0; + let idx; + let length = filter.length; + while ((idx = text.indexOf(filter, start)) > -1 && idx < length) + { + for (let end in util.range(idx + length, text.length + 1)) + substrings.push(text.substring(idx, end)); + start = idx + 1; + } + } + substrings = items.reduce(function (res, {text: text}) + res.filter(function (str) compare(fixCase(text), str)), + substrings); + return this._substrings = substrings; + }, + advance: function advance(count) { this.offset += count; @@ -243,7 +329,7 @@ CompletionContext.prototype = { let cache = this.cache.rows; let reverse = start > end; start = Math.max(0, start || 0); - end = Math.min(items.length, end ? end : items.length); + end = Math.min(items.length, end != null ? end : items.length); return util.map(util.range(start, end, reverse), function (i) cache[i] = cache[i] || util.xmlToDom(self.createRow(items[i]), doc)); }, @@ -281,6 +367,19 @@ CompletionContext.prototype = { catch (e) {} }, + match: function (str) + { + let filter = this.filter; + if (this.ignoreCase) + { + filter = filter.toLowerCase(); + str = str.toLowerCase(); + } + if (this.anchored) + return str.substr(0, filter.length) == filter; + return str.indexOf(filter) > -1; + }, + reset: function reset() { let self = this; @@ -390,17 +489,8 @@ function Completion() //{{{ * wrapped in @last after @offset characters are sliced * off of it and it's quoted. */ - this.objectKeys = function objectKeys(objects) + this.objectKeys = function objectKeys(obj) { - if (!(objects instanceof Array)) - objects = [objects]; - - let [obj, key] = objects; - let cache = this.context.cache.objects || {}; - this.context.cache.objects = cache; - if (key in cache) - return cache[key]; - // Things we can dereference if (["object", "string", "function"].indexOf(typeof obj) == -1) return []; @@ -430,29 +520,7 @@ function Completion() //{{{ key = ""; item.key = key; }); - return cache[key] = compl; - } - - this.filter = function filter(context, compl, name, anchored, key, last, offset) - { - context.title = [name]; - context.anchored = anchored; - context.filter = key; - - if (last != undefined) // Escaping the key (without adding quotes), so it matches the escaped completions. - key = util.escapeString(key.substr(offset), ""); - - if (last != undefined) // Prepend the quote delimiter to the substrings list, so it's not stripped on - substrings = substrings.map(function (s) last + s); - - let res; - if (last != undefined) // We're looking for a quoted string, so, strip whatever prefix we have and quote the rest - res = compl.map(function (a) [util.escapeString(a[0].substr(offset), last), a[1]]); - else // We're not looking for a quoted string, so filter out anything that's not a valid identifier - res = compl.filter(function isIdent(a) /^[\w$][\w\d$]*$/.test(a[0])); - if (!anchored) - res = res.filter(function ([k]) util.compareIgnoreCase(k.substr(0, key.length), key)); - context.completions = res; + return compl; } this.eval = function eval(arg, key, tmp) @@ -697,19 +765,46 @@ function Completion() //{{{ return [dot + 1 + space.length, obj, key]; } + 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); + + if (last != undefined) // Escaping the key (without adding quotes), so it matches the escaped completions. + key = util.escapeString(key.substr(offset), ""); + + // FIXME + if (last != undefined) // Prepend the quote delimiter to the substrings list, so it's not stripped on + substrings = substrings.map(function (s) last + s); + + let res; + if (last != undefined) // We're looking for a quoted string, so, strip whatever prefix we have and quote the rest + context.quote = function (text) util.escapeString(text, 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)); + } + function complete(objects, key, compl, string, last) { for (let [,obj] in Iterator(objects)) { - obj[3] = compl || this.objectKeys(obj); - this.context.fork(obj[1], top[OFFSET], this, "filter", - obj[3], obj[1], true, key + (string || ""), last, key.length); + this.context.fork(obj[1], top[OFFSET], this, fill, obj[0], obj[1], compl, + true, key + (string || ""), last, key.length); } for (let [,obj] in Iterator(objects)) { obj[1] += " (substrings)"; - this.context.fork(obj[1], top[OFFSET], this, "filter", - obj[3], obj[1], false, key + (string || ""), last, key.length); + this.context.fork(obj[1], top[OFFSET], this, fill, obj[0], obj[1], compl, + false, key + (string || ""), last, key.length); } } @@ -822,102 +917,6 @@ function Completion() //{{{ }; let javascript = new Javascript(); - function buildSubstrings(str, filter) - { - if (substrings.length) - { - substrings = substrings.filter(function strIndex(s) str.indexOf(s) >= 0); - return; - } - if (filter == "") - return; - let length = filter.length; - let start = 0; - let idx; - while ((idx = str.indexOf(filter, start)) > -1) - { - for (let end in util.range(idx + length, str.length + 1)) - substrings.push(str.substring(idx, end)); - start = idx + 1; - } - } - - // function uses smartcase - // list = [ [['com1', 'com2'], 'text'], [['com3', 'com4'], 'text'] ] - function buildLongestCommonSubstring(list, filter, favicon) - { - var filtered = []; - - var ignorecase = false; - if (filter == filter.toLowerCase()) - ignorecase = true; - - var longest = false; - if (options["wildmode"].indexOf("longest") >= 0) - longest = true; - - for (let [,item] in Iterator(list)) - { - let text = completion.getKey(item, "text"); - var complist = text instanceof Array ? text : [text]; - for (let [,compitem] in Iterator(complist)) - { - let str = !ignorecase ? compitem : String(compitem).toLowerCase(); - - if (str.indexOf(filter) == -1) - continue; - - item.text = compitem; - filtered.push(item); - - if (longest) - buildSubstrings(str, filter); - break; - } - } - return filtered; - } - - // this function is case sensitive - function buildLongestStartingSubstring(list, filter, favicon) - { - var filtered = []; - - var longest = false; - if (options["wildmode"].indexOf("longest") >= 0) - longest = true; - - for (let [,item] in Iterator(list)) - { - let text = completion.getKey(item, "text"); - var complist = text instanceof Array ? text : [text]; - for (let [,compitem] in Iterator(complist)) - { - if (compitem.substr(0, filter.length) != filter) - continue; - - item.text = compitem; - filtered.push(item); - - if (longest) - { - if (substrings.length == 0) - { - var length = compitem.length; - for (let k = filter.length; k <= length; k++) - substrings.push(compitem.substring(0, k)); - } - else - { - substrings = substrings.filter(function strIndex(s) compitem.indexOf(s) == 0); - } - } - break; - } - } - return filtered; - } - /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -930,8 +929,7 @@ function Completion() //{{{ setFunctionCompleter: function setFunctionCompleter(funcs, completers) { - if (!(funcs instanceof Array)) - funcs = [funcs]; + funcs = Array.concat(funcs); for (let [,func] in Iterator(funcs)) { func.liberatorCompleter = function liberatorCompleter(func, obj, string, args) { @@ -958,15 +956,6 @@ function Completion() //{{{ return context.items.map(function (i) i.item); }, - // generic filter function, also builds substrings needed - // for :set wildmode=list:longest, if necessary - filter: function filter(array, filter, matchFromBeginning) - { - if (matchFromBeginning) - return buildLongestStartingSubstring(array, filter); - return buildLongestCommonSubstring(array, filter); - }, - // cancel any ongoing search cancel: function cancel() { @@ -1009,9 +998,9 @@ function Completion() //{{{ for (let [,elem] in Iterator(urls)) { let item = elem.item || elem; // Kludge - var url = item.url || ""; - var title = item.title || ""; - var tags = item.tags || []; + let url = item.url || ""; + let title = item.title || ""; + let tags = item.tags || []; if (ignorecase) { url = url.toLowerCase(); @@ -1032,13 +1021,6 @@ function Completion() //{{{ continue; } - // TODO: refactor out? And just build if wildmode contains longest? - // Of course --Kris - if (substrings.length == 0) // Build the substrings - buildSubstrings(url, filter); - else - substrings = substrings.filter(function strIndex(s) url.indexOf(s) >= 0); - filtered.push(elem); } @@ -1083,20 +1065,19 @@ function Completion() //{{{ ////////////////////// COMPLETION TYPES //////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - autocmdEvent: function autocmdEvent(filter) [0, this.filter(config.autocommands, filter)], + autocmdEvent: function autocmdEvent(context) + { + context.completions = config.autocommands; + }, bookmark: function bookmark(context, tags) { context.title = ["Bookmark", "Title"]; context.format = bookmarks.format; context.completions = bookmarks.get(context.filter) + context.filters = []; if (tags) - { - let filterFunc = context.filterFunc; - context.filterFunc = function (items, filter, anchored) - filterFunc.call(this, items, filter, anchored) - .filter(function ({item: item}) tags.every(function (tag) item.tags.indexOf(tag) > -1)); - } + context.filters.push(function ({item: item}) tags.every(function (tag) item.tags.indexOf(tag) > -1)); }, buffer: function buffer(context) @@ -1153,24 +1134,26 @@ function Completion() //{{{ context.completions = [k for (k in commands)]; }, - dialog: function dialog(filter) [0, this.filter(config.dialogs, filter)], + dialog: function dialog(context) + { + context.title = ["Dialog"]; + context.completions = config.dialogs; + }, directory: function directory(context, tail) { this.file(context, tail); - context.completions = context.completions.filter(function (i) i[1] == "Directory"); + context.filters.push(function (item) this.getKey(item, "description") == "Directory"); }, - environment: function environment(filter) + environment: function environment(context) { let command = liberator.has("Win32") ? "set" : "env"; let lines = io.system(command).split("\n"); - lines.pop(); - let vars = lines.map(function (line) (line.match(/([^=]+)=(.+)/) || []).slice(1)); - - return [0, this.filter(vars, filter)]; + context.title = ["Environment Variable", "Value"]; + context.generate = function () lines.map(function (line) (line.match(/([^=]+)=(.+)/) || []).slice(1)); }, // provides completions for ex commands, including their arguments @@ -1238,7 +1221,6 @@ function Completion() //{{{ context.keys = { text: 0, description: 1, icon: 2 }; context.anchored = true; context.key = dir; - context.quote = function (text) text.replace(" ", "\\ ", "g"); context.generate = function generate() { context.cache.dir = dir; @@ -1312,7 +1294,6 @@ function Completion() //{{{ ]; context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING; let filter = context.filter; - context.completions.forEach(function ([item]) buildSubstrings(item, filter)); }); completionService.stopSearch(); completionService.startSearch(context.filter, "", context.result, { @@ -1428,33 +1409,31 @@ function Completion() //{{{ } }, - sidebar: function sidebar(filter) + sidebar: function sidebar(context) { let menu = document.getElementById("viewSidebarMenu"); - let panels = Array.map(menu.childNodes, function (n) [n.label, ""]); - - return [0, this.filter(panels, filter)]; + context.title = ["Sidebar Panel"]; + context.completions = Array.map(menu.childNodes, function (n) [n.label, ""]); }, - alternateStylesheet: function alternateStylesheet(filter) + alternateStylesheet: function alternateStylesheet(context) { - let completions = buffer.alternateStyleSheets.map( - function (stylesheet) [stylesheet.title, stylesheet.href || "inline"] - ); + context.title = ["Stylesheet", "Location"]; + context.keys = { text: "title", description: function (item) item.href }; // unify split style sheets + let completions = buffer.alternateStyleSheets; completions.forEach(function (stylesheet) { - completions = completions.filter(function (completion) { - if (stylesheet[0] == completion[0] && stylesheet[1] != completion[1]) + stylesheet.href = stylesheet.href || "inline"; + completions = completions.filter(function (sheet) { + if (stylesheet.title == sheet.title && stylesheet != sheet) { - stylesheet[1] += ", " + completion[1]; + stylesheet.href += ", " + sheet.href; return false; } return true; }); }); - - return [0, this.filter(completions, filter)]; }, // filter a list of urls @@ -1482,11 +1461,11 @@ function Completion() //{{{ this.urlCompleters[opt] = UrlCompleter.apply(null, Array.slice(arguments)); }, - userCommand: function userCommand(filter) + userCommand: function userCommand(context) { - let cmds = commands.getUserCommands(); - cmds = cmds.map(function (cmd) [cmd.name, ""]); - return [0, this.filter(cmds, filter)]; + context.title = ["User Command", "Definition"]; + context.keys = { text: "name", description: "replacementText" }; + context.completions = commands.getUserCommands(); }, userMapping: function userMapping(context, args, modes) @@ -1494,7 +1473,7 @@ function Completion() //{{{ if (args.completeArg == 0) { let maps = [[m.names[0], ""] for (m in mappings.getUserIterator(modes))]; - context.completions = this.filter(maps, args.arguments[0]); + context.completions = maps; } } // }}} diff --git a/content/events.js b/content/events.js index 0c4b0fcd..7d1c7b8e 100644 --- a/content/events.js +++ b/content/events.js @@ -113,7 +113,7 @@ function AutoCommands() //{{{ { argCount: 3, bang: true, - completer: function (context) completion.autocmdEvent(context.filter), + completer: function (context) completion.autocmdEvent(context), literal: true }); @@ -125,7 +125,7 @@ function AutoCommands() //{{{ commands.get("doautocmd").action.call(this, args); }, { - completer: function (context) completion.autocmdEvent(context.filter), + completer: function (context) completion.autocmdEvent(context), literal: true } ); @@ -163,7 +163,7 @@ function AutoCommands() //{{{ } }, { - completer: function (context) completion.autocmdEvent(context.filter), + completer: function (context) completion.autocmdEvent(context), literal: true } ); diff --git a/content/liberator-overlay.js b/content/liberator-overlay.js index d2c8bbe0..3dae5c79 100644 --- a/content/liberator-overlay.js +++ b/content/liberator-overlay.js @@ -7,7 +7,7 @@ var loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"] .getService(Components.interfaces.mozIJSSubScriptLoader); - function load(script) + function load(script, i) { try { @@ -18,6 +18,8 @@ if (Components.utils.reportError) Components.utils.reportError(e); dump("liberator: Loading script " + script + ": " + e + "\n"); + if (!i || i < 3) + return load(script, i + 1); // Sometimes loading (seemingly randomly) fails } } diff --git a/content/liberator.js b/content/liberator.js index a44f7041..684807af 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -203,7 +203,7 @@ const liberator = (function () //{{{ { argCount: "1", bang: true, - completer: function (context) completion.dialog(context.filter) + completer: function (context) completion.dialog(context) }); // TODO: move this diff --git a/content/mail.js b/content/mail.js index 07632bf9..3be0fbc5 100644 --- a/content/mail.js +++ b/content/mail.js @@ -91,18 +91,10 @@ function Mail() //{{{ function getFolderCompletions(filter) { - var completions = []; - var folders = mail.getFolders(filter); - - for (let folder = 0; folder < folders.length; folder++) - { - completions.push([folders[folder].server.prettyName + ": " - + folders[folder].name, - "Unread: " + folders[folder].getNumUnread(false)]); - } - - //return [0, completion.filter(completions, filter)]; - return [0, completions]; + let folders = mail.getFolders(filter); + context.completions = folders.map(function (folder) + [folder.server.prettyName + ": " + folder.name, + "Unread: " + folder.getNumUnread(false)]); } function getCurrentFolderIndex() @@ -684,7 +676,7 @@ function Mail() //{{{ SelectFolder(folder.URI); }, { - completer: function (context) getFolderCompletions(context.filter), + completer: function (context) getFolderCompletions(context), count: true }); @@ -726,12 +718,12 @@ function Mail() //{{{ commands.add(["copy[to]"], "Copy selected messages", function (args) { moveOrCopy(true, args.string); }, - { completer: function (context) getFolderCompletions(context.filter) }); + { completer: function (context) getFolderCompletions(context) }); commands.add(["move[to]"], "Move selected messages", function (args) { moveOrCopy(false, args.string); }, - { completer: function (context) getFolderCompletions(context.filter) }); + { completer: function (context) getFolderCompletions(context) }); commands.add(["empty[trash]"], "Empty trash of the current account", diff --git a/content/options.js b/content/options.js index 6e1720b6..014f19b8 100644 --- a/content/options.js +++ b/content/options.js @@ -52,6 +52,7 @@ function Option(names, description, type, defaultValue, extraInfo) //{{{ this.getter = extraInfo.getter || null; this.completer = extraInfo.completer || null; this.validator = extraInfo.validator || null; + this.checkHas = extraInfo.checkHas || null; // this property is set to true whenever the option is first set // useful to see whether it was changed by some rc file @@ -73,6 +74,18 @@ function Option(names, description, type, defaultValue, extraInfo) //{{{ if (this.globalvalue == undefined) this.globalvalue = this.defaultValue; + this.__defineGetter__("values", function () this.getValues(this.scope)); + + this.getValues = function (scope) + { + let value = this.get(scope); + if (this.type == "stringlist") + return value.split(","); + if (this.type == "charlist") + return Array.slice(value); + return value; + }; + this.get = function (scope) { if (scope) @@ -124,18 +137,20 @@ function Option(names, description, type, defaultValue, extraInfo) //{{{ this.hasChanged = true; }; - this.has = function () - { - let value = this.value; - if (this.type == "stringlist") - value = this.value.split(","); - /* Return whether some argument matches */ - return Array.some(arguments, function (val) value.indexOf(val) >= 0); - }; - this.__defineGetter__("value", this.get); this.__defineSetter__("value", this.set); + this.has = function () + { + let self = this; + let test = function (val) values.indexOf(val) >= 0; + if (this.checkHas) + test = function (val) values.some(function (value) self.checkHas(value, val)); + let values = this.values; + /* Return whether some argument matches */ + return Array.some(arguments, function (val) test(val)) + }; + this.hasName = function (name) { return this.names.indexOf(name) >= 0; @@ -695,28 +710,18 @@ function Options() //{{{ if (special) // list completions for about:config entries { - var prefs = Components.classes["@mozilla.org/preferences-service;1"] - .getService(Components.interfaces.nsIPrefBranch); - var prefArray = prefs.getChildList("", { value: 0 }); - prefArray.sort(); - - if (filter.length > 0 && filter.lastIndexOf("=") == filter.length - 1) + if (filter[filter.length - 1] == "=") { - for (let [,name] in Iterator(prefArray)) - { - if (name.match("^" + filter.substr(0, filter.length - 1) + "$" )) - { - let value = options.getPref(name) + ""; - return [filter.length + 1, [[value, ""]]]; - } - } - return [0, []]; + context.advance(filter.length); + context.completions = [options.getPref(filter.substr(0, filter.length - 1)), "Current Value"]; + return; } - optionCompletions = prefArray.map(function (pref) - [pref, options.getPref(pref)]); - - return [0, completion.filter(optionCompletions, filter)]; + let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + context.keys = [function (pref) pref, function (pref) options.getPref(pref)]; + context.completions = prefs.getChildList("", { value: 0 }); + return; } let prefix = (filter.match(/^(no|inv)/) || [""])[0]; @@ -728,19 +733,13 @@ function Options() //{{{ let opts = (opt for (opt in options) if ((opt.scope & scope) && (!prefix || opt.type == "boolean" || prefix == "inv" && /list$/.test(opt.type)))); - if (!filter) + if (filter.indexOf("=") == -1) { - return [0, [[prefix + option.name, option.description] for (option in opts)]]; - } - else if (filter.indexOf("=") == -1) - { - for (let option in opts) - optionCompletions.push([[prefix + name, option.description] - for each (name in option.names) - if (name.indexOf(filter) == 0)]); - optionCompletions = util.Array.flatten(optionCompletions); - - return [0, completion.filter(optionCompletions, prefix + filter, true)]; + context.title = ["Option"]; + context.quote = function (name) prefix + name; + context.keys = { text: "names", description: "description" }; + context.completions = [opt for (opt in opts)]; + return; } else if (prefix == "no") return; @@ -754,7 +753,7 @@ function Options() //{{{ context.highlight(0, name.length, "SPELLCHECK"); if (opt.get || opt.reset || !option || prefix) - return [0, []]; + return; let completer = option.completer; @@ -773,8 +772,8 @@ function Options() //{{{ break; } - len = filter.length - len; - filter = filter.substr(len); + context.advance(filter.length - len); + filter = context.filter; /* Not vim compatible, but is a significant enough improvement * that it's worth breaking compatibility. @@ -800,7 +799,9 @@ function Options() //{{{ } } } - return [len, completion.filter(completions, filter, true)]; + context.compare = function (a, b) 0; + context.title = ["Option Value"]; + context.completions = completions; }, literal: true, serial: function () [ diff --git a/content/style.js b/content/style.js index 932b6041..54c9a623 100644 --- a/content/style.js +++ b/content/style.js @@ -463,15 +463,9 @@ liberator.registerObserver("load_commands", function () }, { argCount: 2, - // FIXME: Ugly. - completer: function (context) [0, completion.filter( - [[i, <>{s.sites.join(",")}: {s.css.replace("\n", "\\n")}] - for ([i, s] in styles.userSheets) - ] - .concat([[s, ""] for each (s in styles.sites)]) - , context.filter)], + completer: function (context) { context.completions = styles.sites.map(function (site) [site, ""]); }, literal: true, - options: [[["-index", "-i"], commands.OPTION_INT, null, function () [[k, v.name || v.sites.join(",") + " " + v.css] for ([k, v] in Iterator(styles.userNames))]], + options: [[["-index", "-i"], commands.OPTION_INT, null, function () [[i, <>{s.sites.join(",")}: {s.css.replace("\n", "\\n")}] for ([i, s] in styles.userSheets)]], [["-name", "-n"], commands.OPTION_STRING, null, function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))]]] }); diff --git a/content/ui.js b/content/ui.js index 3a7f7cd4..bf2a492f 100644 --- a/content/ui.js +++ b/content/ui.js @@ -128,12 +128,12 @@ function CommandLine() //{{{ historyIndex = UNINITIALIZED; // TODO: call just once, and not on each - let wildmode = options["wildmode"].split(","); - let wildType = wildmode[Math.min(wildIndex++, wildmode.length - 1)]; + let wildmode = options.get("wildmode"); + let wildType = wildmode.values[Math.min(wildIndex++, wildmode.values.length - 1)]; - let hasList = /^list(:|$)/.test(wildType); - let longest = /(^|:)longest$/.test(wildType); - let full = !longest && /(^|:)full/.test(wildType); + let hasList = wildmode.checkHas(wildType, "list"); + let longest = wildmode.checkHas(wildType, "longest"); + let full = !longest && wildmode.checkHas(wildType, "full"); // we need to build our completion list first if (completionIndex == UNINITIALIZED) @@ -196,7 +196,7 @@ function CommandLine() //{{{ { var compl = null; if (longest && completions.items.length > 1) - compl = completion.longestSubstring; + compl = completions.longestSubstring; else if (full) compl = completions.items[completionIndex].text; else if (completions.items.length == 1) @@ -207,7 +207,7 @@ function CommandLine() //{{{ setCommand(command.substring(0, completions.start) + compl + completionPostfix); commandWidget.selectionStart = commandWidget.selectionEnd = completions.start + compl.length; if (longest) - liberator.triggerCallback("change", currentExtendedMode, this.getCommand()); + liberator.triggerCallback("change", currentExtendedMode, commandline.getCommand()); // Start a new completion in the next iteration. Useful for commands like :source // RFC: perhaps the command can indicate whether the completion should be restarted @@ -497,9 +497,14 @@ function CommandLine() //{{{ }, validator: function validator(value) { - return value.split(",").every( - function (item) /^(full|longest|list|list:full|list:longest|)$/.test(item) - ); + let self = this; + return value.split(",").every(function (opt) + self.completer().some(function ([key]) key == opt)) + }, + checkHas: function (value, val) + { + let [first, second] = value.split(":", 2); + return first == val || second == val; } }); @@ -1335,8 +1340,11 @@ function ItemList(id) //{{{
{context.createRow(context.title || [], "hl-CompTitle")}
+
+
); + context.cache.arrows = context.cache.dom.getElementsByTagName("span"); completionBody.appendChild(context.cache.dom); }); } @@ -1362,20 +1370,24 @@ function ItemList(id) //{{{ let off = 0; function getRows(context) { + function fix(n) Math.max(0, Math.min(len, n)); let len = context.items.length; let start = off; off += len; - return context.getRows(offset - start, endIndex - start, doc); + return [fix(offset - start), fix(endIndex - start)]; } items.contextList.forEach(function fill_eachContext(context) { let dom = context.cache.dom; if (!dom) return; + let [start, end] = getRows(context); + context.cache.arrows[0].style.display = (start == 0) ? "none" : "block"; + context.cache.arrows[1].style.display = (end == context.items.length) ? "none" : "block"; let d = stuff.cloneNode(true); - for (let [,row] in Iterator(getRows(context))) + for (let [,row] in Iterator(context.getRows(start, end, doc))) d.appendChild(row); - dom.replaceChild(d, dom.childNodes[3] || dom.childNodes[1]); + dom.replaceChild(d, dom.getElementsByTagName("div")[1]); }); noCompletions.style.display = off > 0 ? "none" : "block"; diff --git a/content/util.js b/content/util.js index eeef060a..98e62a62 100644 --- a/content/util.js +++ b/content/util.js @@ -38,13 +38,8 @@ const util = { //{{{ return obj; }, - // flatten an array: [["foo", "bar"], ["baz"]] -> ["foo", "bar", "baz"] - flatten: function (ary) - { - if (ary.length == 0) - return []; - return Array.concat.apply(Array, ary); - }, + // flatten an array: [["foo", ["bar"]], ["baz"], "quux"] -> ["foo", ["bar"], "baz", "quux"] + flatten: function (ary) Array.concat.apply([], ary), iterator: function (ary) { diff --git a/content/vimperator.js b/content/vimperator.js index 781c8822..59a5e465 100644 --- a/content/vimperator.js +++ b/content/vimperator.js @@ -343,7 +343,7 @@ const config = { //{{{ }, { argCount: "+", - completer: function (context) completion.sidebar(context.filter), + completer: function (context) completion.sidebar(context), literal: true });