diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/content/bookmarks.js | 12 | ||||
-rw-r--r-- | common/content/commandline.js | 80 | ||||
-rw-r--r-- | common/content/commands.js | 4 | ||||
-rw-r--r-- | common/content/completion.js | 93 | ||||
-rw-r--r-- | common/content/help.xsl | 12 | ||||
-rw-r--r-- | common/content/io.js | 10 | ||||
-rw-r--r-- | common/content/options.js | 405 | ||||
-rw-r--r-- | common/content/sanitizer.js | 5 | ||||
-rw-r--r-- | common/content/style.js | 2 | ||||
-rw-r--r-- | common/content/tabs.js | 6 | ||||
-rw-r--r-- | common/content/template.js | 2 | ||||
-rw-r--r-- | common/locale/en-US/developer.xml | 2 | ||||
-rw-r--r-- | common/locale/en-US/options.xml | 109 | ||||
-rw-r--r-- | common/locale/en-US/various.xml | 28 |
14 files changed, 469 insertions, 301 deletions
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 077c1e8c..207cb3b1 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -598,6 +598,18 @@ const Bookmarks = Module("bookmarks", { context.completions = [["", "Don't perform searches by default"]].concat(context.completions); } }); + + options.add(["suggestengines"], + "Engine Alias which has a feature of suggest", + "stringlist", "google", + { + completer: function completer(value) { + let engines = services.get("browserSearch").getEngines({}) + .filter(function (engine) engine.supportsResponseType("application/x-suggestions+json")); + + return engines.map(function (engine) [engine.alias, engine.description]); + } + }); }, completion: function () { completion.bookmark = function bookmark(context, tags, extra) { diff --git a/common/content/commandline.js b/common/content/commandline.js index 79a4b4f2..16d06985 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -102,7 +102,7 @@ const CommandLine = Module("commandline", { }); this._autocompleteTimer = new Timer(200, 500, function autocompleteTell(tabPressed) { - if (!events.feedingKeys && self._completions && options.get("wildoptions").has("auto")) { + if (!events.feedingKeys && self._completions && options.get("autocomplete").values.length) { self._completions.complete(true, false); self._completions.itemList.show(); } @@ -1516,84 +1516,6 @@ const CommandLine = Module("commandline", { options.add(["showmode", "smd"], "Show the current mode in the command line", "boolean", true); - - options.add(["suggestengines"], - "Engine Alias which has a feature of suggest", - "stringlist", "google", - { - completer: function completer(value) { - let engines = services.get("browserSearch").getEngines({}) - .filter(function (engine) engine.supportsResponseType("application/x-suggestions+json")); - - return engines.map(function (engine) [engine.alias, engine.description]); - } - }); - - options.add(["complete", "cpt"], - "Items which are completed at the :open prompts", - "charlist", typeof(config.defaults["complete"]) == "string" ? config.defaults["complete"] : "slf", - { - completer: function (context) array(values(completion.urlCompleters)) - }); - - options.add(["wildcase", "wic"], - "Completion case matching mode", - "string", "smart", - { - completer: function () [ - ["smart", "Case is significant when capital letters are typed"], - ["match", "Case is always significant"], - ["ignore", "Case is never significant"] - ] - }); - - options.add(["wildignore", "wig"], - "List of file patterns to ignore when completing files", - "stringlist", "", - { - validator: function validator(values) { - // TODO: allow for escaping the "," - try { - RegExp("^(" + values.join("|") + ")$"); - return true; - } - catch (e) { - return false; - } - } - }); - - options.add(["wildmode", "wim"], - "Define how command line completion works", - "stringlist", "list:full", - { - completer: function (context) [ - // Why do we need ""? - ["", "Complete only the first match"], - ["full", "Complete the next full match"], - ["longest", "Complete to longest common string"], - ["list", "If more than one match, list all matches"], - ["list:full", "List all and complete first match"], - ["list:longest", "List all and complete common string"] - ], - checkHas: function (value, val) { - let [first, second] = value.split(":", 2); - return first == val || second == val; - } - }); - - options.add(["wildoptions", "wop"], - "Change how command line completion is done", - "stringlist", "", - { - completer: function completer(value) { - return [ - ["", "Default completion that won't show or sort the results"], - ["auto", "Automatically show this._completions while you are typing"], - ["sort", "Always sort the completion list"] - ]; - } - }); }, styles: function () { let fontSize = util.computedStyle(document.getElementById(config.mainWindowId)).fontSize; diff --git a/common/content/commands.js b/common/content/commands.js index 0b15f55e..f085507c 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -894,10 +894,10 @@ const Commands = Module("commands", { } [prefix] = context.filter.match(/^(?:\w*[\s!]|!)\s*/); - let cmdContext = context.fork(cmd, prefix.length); + let cmdContext = context.fork(command.name, prefix.length); let argContext = context.fork("args", prefix.length); args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang }); - if (args) { + if (args && !cmdContext.waitingForTab) { // FIXME: Move to parseCommand args.count = count; args.bang = bang; diff --git a/common/content/completion.js b/common/content/completion.js index 16aa7398..cd25a49a 100644 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -34,6 +34,11 @@ const CompletionContext = Class("CompletionContext", { if (editor instanceof this.constructor) { let parent = editor; name = parent.name + "/" + name; + + this.autoComplete = options.get("autocomplete").getKey(name); + this.sortResults = options.get("wildsort").getKey(name); + this.wildcase = options.get("wildcase").getKey(name); + this.contexts = parent.contexts; if (name in this.contexts) self = this.contexts[name]; @@ -146,6 +151,8 @@ const CompletionContext = Class("CompletionContext", { this.top = this; this.__defineGetter__("incomplete", function () this.contextList.some(function (c) c.parent && c.incomplete)); this.__defineGetter__("waitingForTab", function () this.contextList.some(function (c) c.parent && c.waitingForTab)); + this.__defineSetter__("incomplete", function (val) {}); + this.__defineSetter__("waitingForTab", function (val) {}); this.reset(); } /** @@ -333,7 +340,7 @@ const CompletionContext = Class("CompletionContext", { get ignoreCase() { if ("_ignoreCase" in this) return this._ignoreCase; - let mode = options["wildcase"]; + let mode = this.wildcase; if (mode == "match") return this._ignoreCase = false; if (mode == "ignore") @@ -367,7 +374,7 @@ const CompletionContext = Class("CompletionContext", { if (this.maxItems) filtered = filtered.slice(0, this.maxItems); - if (options.get("wildoptions").has("sort") && this.compare) + if (this.sortResults && this.compare) filtered.sort(this.compare); let quote = this.quote; if (quote) @@ -498,8 +505,14 @@ const CompletionContext = Class("CompletionContext", { completer = self[completer]; let context = CompletionContext(this, name, offset); this.contextList.push(context); - if (completer) + + if (!context.autoComplete && !context.tabPressed && context.editor) + context.waitingForTab = true; + else if (completer) return completer.apply(self || this, [context].concat(Array.slice(arguments, arguments.callee.length))); + + if (completer) + return null; return context; }, @@ -741,6 +754,80 @@ const Completion = Module("completion", { //}}} }, { UrlCompleter: Struct("name", "description", "completer") +}, { + commands: function () { + commands.add(["contexts"], + "List the completion contexts used during completion of an ex command", + function (args) { + commandline.echo(template.commandOutput( + <div highlight="Completions"> + { template.completionRow(["Context", "Title"], "CompTitle") } + { template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) } + </div>), + null, commandline.FORCE_MULTILINE); + + }, + { + argCount: "1", + completer: function (context, args) { + let PREFIX = "/ex/contexts"; + context.fork("ex", 0, completion, "ex"); + completion.contextList = [[k.substr(PREFIX.length), v.title[0]] for ([k, v] in iter(context.contexts)) if (k.substr(0, PREFIX.length) == PREFIX)]; + }, + literal: 0 + }); + }, + options: function () { + options.add(["autocomplete", "au"], + "Automatically update the completion list on any key press", + "regexlist", ".*"); + + options.add(["complete", "cpt"], + "Items which are completed at the :open prompts", + "charlist", typeof(config.defaults["complete"]) == "string" ? config.defaults["complete"] : "slf", + { + completer: function (context) array(values(completion.urlCompleters)) + }); + + options.add(["wildcase", "wic"], + "Completion case matching mode", + "regexmap", "smart", + { + completer: function () [ + ["smart", "Case is significant when capital letters are typed"], + ["match", "Case is always significant"], + ["ignore", "Case is never significant"] + ] + }); + + options.add(["wildmode", "wim"], + "Define how command line completion works", + "stringlist", "list:full", + { + completer: function (context) [ + // Why do we need ""? + // Because its description is useful during completion. --Kris + ["", "Complete only the first match"], + ["full", "Complete the next full match"], + ["longest", "Complete to longest common string"], + ["list", "If more than one match, list all matches"], + ["list:full", "List all and complete first match"], + ["list:longest", "List all and complete common string"] + ], + checkHas: function (value, val) { + let [first, second] = value.split(":", 2); + return first == val || second == val; + }, + has: function () { + test = function (val) this.values.some(function (value) this.checkHas(value, val), this); + return Array.some(arguments, test, this); + } + }); + + options.add(["wildsort", "wis"], + "Regexp list of which contexts to sort", + "regexlist", ".*"); + } }); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/help.xsl b/common/content/help.xsl index 1ad7ee76..e9766465 100644 --- a/common/content/help.xsl +++ b/common/content/help.xsl @@ -202,7 +202,7 @@ <xsl:variable name="type" select="preceding-sibling::liberator:type[1] | following-sibling::liberator:type[1]"/> <span liberator:highlight="HelpDefault">(default:<xsl:text> </xsl:text> <xsl:choose> - <xsl:when test="starts-with($type, 'string')"> + <xsl:when test="starts-with($type, 'string') or starts-with($type, 'regex')"> <span liberator:highlight="HelpString"><xsl:apply-templates/></span> </xsl:when> <xsl:otherwise> @@ -351,9 +351,13 @@ <xsl:template match="liberator:ex" mode="pass-2"> <span liberator:highlight="HelpEx"> - <xsl:call-template name="linkify-tag"> - <xsl:with-param name="contents" select="."/> - </xsl:call-template> + <xsl:variable name="tag" select="str:tokenize(text(), ' [!')[1]"/> + <a href="liberator://help-tag/{$tag}" style="color: inherit;"> + <xsl:if test="contains($tags, concat(' ', $tag, ' '))"> + <xsl:attribute name="href">#<xsl:value-of select="$tag"/></xsl:attribute> + </xsl:if> + <xsl:apply-templates/> + </a> </span> </xsl:template> diff --git a/common/content/io.js b/common/content/io.js index 3e5bd21a..713423f5 100644 --- a/common/content/io.js +++ b/common/content/io.js @@ -240,7 +240,7 @@ const File = Class("File", { */ MODE_EXCL: 0x80, - expandPathList: function (list) list.split(",").map(this.expandPath).join(","), + expandPathList: function (list) list.map(this.expandPath), expandPath: function (path, relative) { @@ -1028,8 +1028,8 @@ lookup: b.isdir - a.isdir || String.localeCompare(a.text, b.text); if (options["wildignore"]) { - let wigRegexp = RegExp("(^" + options.get("wildignore").values.join("|") + ")$"); - context.filters.push(function ({item: f}) f.isDirectory() || !wigRegexp.test(f.leafName)); + let wig = options.get("wildignore"); + context.filters.push(function ({item: f}) f.isDirectory() || !wig.getKey(this.name)); } // context.background = true; @@ -1099,6 +1099,10 @@ lookup: options.add(["shellcmdflag", "shcf"], "Flag passed to shell when executing :! and :run commands", "string", shellcmdflag); + + options.add(["wildignore", "wig"], + "List of file patterns to ignore when completing files", + "regexlist", ""); } }); diff --git a/common/content/options.js b/common/content/options.js index e304339f..55430c8b 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -22,7 +22,6 @@ * getter - see {@link Option#getter} * completer - see {@link Option#completer} * valdator - see {@link Option#validator} - * checkHas - see {@link Option#checkHas} * @optional * @private */ @@ -33,6 +32,17 @@ const Option = Class("Option", { this.type = type; this.description = description; + if (this.type in Option.getKey) + this.getKey = Option.getKey[this.type]; + + if (this.type in Option.parseValues) + this.parseValues = Option.parseValues[this.type]; + + if (this.type in Option.joinValues) + this.joinValues = Option.joinValues[this.type]; + + this._op = Option.ops[this.type]; + if (arguments.length > 3) this.defaultValue = defaultValue; @@ -44,7 +54,7 @@ const Option = Class("Option", { this.names = array([name, "no" + name] for (name in values(names))).flatten().__proto__; if (this.globalValue == undefined) - this.globalValue = this.defaultValue; + this.globalValue = this.parseValues(this.defaultValue); }, /** @property {value} The option's global value. @see #scope */ @@ -58,13 +68,7 @@ const Option = Class("Option", { * @param {value} value The option value. * @returns {value|string[]} */ - parseValues: function (value) { - if (this.type == "stringlist") - return (value === "") ? [] : value.split(","); - if (this.type == "charlist") - return Array.slice(value); - return value; - }, + parseValues: function (value) value, /** * Returns <b>values</b> packed in the appropriate format for the option @@ -73,16 +77,10 @@ const Option = Class("Option", { * @param {value|string[]} values The option value. * @returns {value} */ - joinValues: function (values) { - if (this.type == "stringlist") - return values.join(","); - if (this.type == "charlist") - return values.join(""); - return values; - }, + joinValues: function (vals) vals, /** @property {value|string[]} The option value or array of values. */ - get values() this.parseValues(this.value), + get values() this.getValues(this.scope), set values(values) this.setValues(values, this.scope), /** @@ -93,7 +91,26 @@ const Option = Class("Option", { * {@link Option#scope}). * @returns {value|string[]} */ - getValues: function (scope) this.parseValues(this.get(scope)), + getValues: function (scope) { + if (scope) { + if ((scope & this.scope) == 0) // option doesn't exist in this scope + return null; + } + else + scope = this.scope; + + let values; + + if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL)) + values = tabs.options[this.name]; + if ((scope & Option.SCOPE_GLOBAL) && (values == undefined)) + values = this.globalValue; + + if (this.getter) + return liberator.trapErrors(this.getter, this, values); + + return values; + }, /** * Sets the option's value from an array of values if the option type is @@ -102,8 +119,22 @@ const Option = Class("Option", { * @param {number} scope The scope to apply these values to (see * {@link Option#scope}). */ - setValues: function (values, scope) { - this.set(this.joinValues(values), scope || this.scope); + setValues: function (newValues, scope, skipGlobal) { + scope = scope || this.scope; + if ((scope & this.scope) == 0) // option doesn't exist in this scope + return; + + if (this.setter) + newValues = liberator.trapErrors(this.setter, this, newValues); + if (newValues === undefined) + return; + + if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL)) + tabs.options[this.name] = newValues; + if ((scope & Option.SCOPE_GLOBAL) && !skipGlobal) + this.globalValue = newValues; + + this.hasChanged = true; }, /** @@ -115,26 +146,7 @@ const Option = Class("Option", { * {@link Option#scope}). * @returns {value} */ - get: function (scope) { - if (scope) { - if ((scope & this.scope) == 0) // option doesn't exist in this scope - return null; - } - else - scope = this.scope; - - let value; - - if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL)) - value = tabs.options[this.name]; - if ((scope & Option.SCOPE_GLOBAL) && (value == undefined)) - value = this.globalValue; - - if (this.getter) - return liberator.trapErrors(this.getter, this, value); - - return value; - }, + get: function (scope) this.joinValues(this.getValues(scope)), /** * Sets the option value to <b>newValue</b> for the specified <b>scope</b>. @@ -145,21 +157,7 @@ const Option = Class("Option", { * @param {number} scope The scope to apply this value to (see * {@link Option#scope}). */ - set: function (newValue, scope) { - scope = scope || this.scope; - if ((scope & this.scope) == 0) // option doesn't exist in this scope - return; - - if (this.setter) - newValue = liberator.trapErrors(this.setter, this, newValue); - - if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL)) - tabs.options[this.name] = newValue; - if ((scope & Option.SCOPE_GLOBAL) && newValue != this.globalValue) - this.globalValue = newValue; - - this.hasChanged = true; - }, + set: function (newValue, scope) this.setValues(this.parseValues(newValue), scope), /** * @property {value} The option's current value. The option's local value, @@ -169,21 +167,15 @@ const Option = Class("Option", { get value() this.get(), set value(val) this.set(val), + getKey: function (key) undefined, + /** * Returns whether the option value contains one or more of the specified * arguments. * * @returns {boolean} */ - 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)); - }, + has: function () Array.some(arguments, function (val) this.values.indexOf(val) >= 0, this), /** * Returns whether this option is identified by <b>name</b>. @@ -216,97 +208,16 @@ const Option = Class("Option", { * @param {boolean} invert Whether this is an invert boolean operation. */ op: function (operator, values, scope, invert) { - let newValue = null; - let self = this; - - switch (this.type) { - case "boolean": - if (operator != "=") - break; - - if (invert) - newValue = !this.value; - else - newValue = values; - break; - - case "number": - // TODO: support floats? Validators need updating. - if (!/^[+-]?(?:0x[0-9a-f]+|0[0-7]+|0|[1-9]\d*)$/i.test(values)) - return "E521: Number required after := " + this.name + "=" + values; - - let value = parseInt(values/* deduce radix */); - - switch (operator) { - case "+": - newValue = this.value + value; - break; - case "-": - newValue = this.value - value; - break; - case "^": - newValue = this.value * value; - break; - case "=": - newValue = value; - break; - } - - break; - - case "charlist": - case "stringlist": - values = Array.concat(values); - switch (operator) { - case "+": - newValue = util.Array.uniq(Array.concat(this.values, values), true); - break; - case "^": - // NOTE: Vim doesn't prepend if there's a match in the current value - newValue = util.Array.uniq(Array.concat(values, this.values), true); - break; - case "-": - newValue = this.values.filter(function (item) values.indexOf(item) == -1); - break; - case "=": - newValue = values; - if (invert) { - let keepValues = this.values.filter(function (item) values.indexOf(item) == -1); - let addValues = values.filter(function (item) self.values.indexOf(item) == -1); - newValue = addValues.concat(keepValues); - } - break; - } - break; + let newValues = this._op(operator, values, scope, invert); - case "string": - switch (operator) { - case "+": - newValue = this.value + values; - break; - case "-": - newValue = this.value.replace(values, ""); - break; - case "^": - newValue = values + this.value; - break; - case "=": - newValue = values; - break; - } - - break; - - default: - return "E685: Internal error: option type `" + this.type + "' not supported"; - } - - if (newValue == null) + if (newValues == null) return "Operator " + operator + " not supported for option type " + this.type; - if (!this.isValidValue(newValue)) + + if (!this.isValidValue(newValues)) return "E474: Invalid argument: " + values; - this.setValues(newValue, scope); + + this.setValues(newValues, scope); return null; }, @@ -319,11 +230,13 @@ const Option = Class("Option", { /** * @property {string} The option's data type. One of: - * "boolean" - Boolean E.g. true - * "number" - Integer E.g. 1 - * "string" - String E.g. "Vimperator" - * "charlist" - Character list E.g. "rb" - * "stringlist" - String list E.g. "homepage,quickmark,tabopen,paste" + * "boolean" - Boolean, e.g., true + * "number" - Integer, e.g., 1 + * "string" - String, e.g., "Vimperator" + * "charlist" - Character list, e.g., "rb" + * "regexlist" - Regex list, e.g., "^foo,bar$" + * "stringmap" - String map, e.g., "key=v,foo=bar" + * "regexmap" - Regex map, e.g., "^key=v,foo$=bar" */ type: null, @@ -370,12 +283,6 @@ const Option = Class("Option", { return Option.validateCompleter.apply(this, arguments); return true; }, - /** - * @property The function called to determine whether the option already - * contains a specified value. - * @see #has - */ - checkHas: null, /** * @property {boolean} Set to true whenever the option is first set. This @@ -410,6 +317,131 @@ const Option = Class("Option", { */ SCOPE_BOTH: 3, + parseRegex: function (val, result) { + let [, bang, val] = /^(!?)(.*)/.exec(val); + let re = RegExp(val); + re.bang = bang; + re.result = arguments.length == 2 ? result : !bang; + return re; + }, + unparseRegex: function (re) re.bang + re.source + (typeof re.result == "string" ? "=" + re.result : ""), + + getKey: { + stringlist: function (k) this.values.indexOf(k) >= 0, + regexlist: function (k) { + for (let re in values(this.values)) + if (re.test(k)) + return re.result; + return null; + } + }, + + joinValues: { + charlist: function (vals) vals.join(""), + stringlist: function (vals) vals.join(","), + stringmap: function (vals) [k + "=" + v for ([k, v] in Iterator(vals))].join(","), + regexlist: function (vals) vals.map(Option.unparseRegex).join(","), + }, + + parseValues: { + number: function (value) Number(value), + boolean: function (value) value == "true" || value == true ? true : false, + charlist: function (value) Array.slice(value), + stringlist: function (value) (value === "") ? [] : value.split(","), + stringmap: function (value) array(v.split("=") for (v in values(value.split(",")))).toObject(), + regexlist: function (value) (value === "") ? [] : value.split(",").map(Option.parseRegex), + regexmap: function (value) value.split(",").map(function (v) v.split("=")) + .map(function ([k, v]) v != null ? Option.parseRegex(k, v) : Option.parseRegex('.?', k)) + }, + + ops: { + boolean: function (operator, values, scope, invert) { + if (operator != "=") + return null; + if (invert) + return !this.value; + return values; + }, + + number: function (operator, values, scope, invert) { + // TODO: support floats? Validators need updating. + if (!/^[+-]?(?:0x[0-9a-f]+|0[0-7]*|[1-9]+)$/i.test(values)) + return "E521: Number required after := " + this.name + "=" + values; + + let value = parseInt(values); + + switch (operator) { + case "+": + return this.value + value; + case "-": + return this.value - value; + case "^": + return this.value * value; + case "=": + return value; + } + return null; + }, + + stringmap: function (operator, values, scope, invert) { + values = Array.concat(values); + orig = [k + "=" + v for ([k, v] in Iterator(this.values))]; + + switch (operator) { + case "+": + return util.Array.uniq(Array.concat(orig, values), true); + case "^": + // NOTE: Vim doesn't prepend if there's a match in the current value + return util.Array.uniq(Array.concat(values, orig), true); + case "-": + return orig.filter(function (item) values.indexOf(item) == -1); + case "=": + if (invert) { + let keepValues = orig.filter(function (item) values.indexOf(item) == -1); + let addValues = values.filter(function (item) self.values.indexOf(item) == -1); + return addValues.concat(keepValues); + } + return values; + } + return null; + }, + + stringlist: function (operator, values, scope, invert) { + values = Array.concat(values); + switch (operator) { + case "+": + return util.Array.uniq(Array.concat(this.values, values), true); + case "^": + // NOTE: Vim doesn't prepend if there's a match in the current value + return util.Array.uniq(Array.concat(values, this.values), true); + case "-": + return this.values.filter(function (item) values.indexOf(item) == -1); + case "=": + if (invert) { + let keepValues = this.values.filter(function (item) values.indexOf(item) == -1); + let addValues = values.filter(function (item) self.values.indexOf(item) == -1); + return addValues.concat(keepValues); + } + return values; + } + return null; + }, + + string: function (operator, values, scope, invert) { + switch (operator) { + case "+": + return this.value + values; + case "-": + return this.value.replace(values, ""); + case "^": + return values + this.value; + case "=": + return values; + } + return null; + } + }, + // TODO: Run this by default? /** * Validates the specified <b>values</b> against values generated by the @@ -423,10 +455,21 @@ const Option = Class("Option", { let res = context.fork("", 0, this, this.completer); if (!res) res = context.allItems.items.map(function (item) [item.text]); + if (this.type == "regexmap") + return Array.concat(values).every(function (re) res.some(function (item) item[0] == re.result)); return Array.concat(values).every(function (value) res.some(function (item) item[0] == value)); } }); +Option.joinValues["regexmap"] = Option.joinValues["regexlist"]; + +Option.getKey["charlist"] = Option.getKey["stringlist"]; +Option.getKey["regexmap"] = Option.getKey["regexlist"]; + +Option.ops["charlist"] = Option.ops["stringlist"]; +Option.ops["regexlist"] = Option.ops["stringlist"]; +Option.ops["regexmap"] = Option.ops["stringlist"]; + /** * @instance options */ @@ -462,7 +505,7 @@ const Options = Module("options", { // Trigger any setters. let opt = options.get(option); if (event == "change" && opt) - opt.set(opt.value, Option.SCOPE_GLOBAL); + opt.setValues(opt.globalValue, Option.SCOPE_GLOBAL, true); } storage.newMap("options", { store: false }); @@ -1008,8 +1051,6 @@ const Options = Module("options", { } } // write access - // NOTE: the behavior is generally Vim compatible but could be - // improved. i.e. Vim's behavior is pretty sloppy to no real benefit else { option.setFrom = modifiers.setFrom || null; @@ -1017,7 +1058,12 @@ const Options = Module("options", { liberator.assert(!opt.valueGiven, "E474: Invalid argument: " + arg); opt.values = !opt.unsetBoolean; } - let res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert); + try { + var res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert); + } + catch (e) { + res = e; + } if (res) liberator.echoerr(res); } @@ -1244,8 +1290,15 @@ const Options = Module("options", { if (!completer) return; - let curValues = curValue != null ? opt.parseValues(curValue) : opt.values; - let newValues = opt.parseValues(context.filter); + try { + var curValues = curValue != null ? opt.parseValues(curValue) : opt.values; + var newValues = opt.parseValues(context.filter); + } + catch (e) { + context.message = "Error: " + e; + context.completions = []; + return; + } let len = context.filter.length; switch (opt.type) { @@ -1253,9 +1306,18 @@ const Options = Module("options", { if (!completer) completer = function () [["true", ""], ["false", ""]]; break; + case "regexlist": + newValues = context.filter.split(","); + // Fallthrough case "stringlist": - let target = newValues.pop(); - len = target ? target.length : 0; + let target = newValues.pop() || ""; + len = target.length; + break; + case "stringmap": + case "regexmap": + let vals = context.filter.split(","); + target = vals.pop() || ""; + len = target.length - (target.indexOf("=") + 1); break; case "charlist": len = 0; @@ -1268,9 +1330,10 @@ const Options = Module("options", { let completions = completer(context); if (!completions) return; + // Not Vim compatible, but is a significant enough improvement // that it's worth breaking compatibility. - if (newValues instanceof Array) { + if (isarray(newValues)) { completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1); switch (op) { case "+": diff --git a/common/content/sanitizer.js b/common/content/sanitizer.js index b0a9eb16..1b3afcc4 100644 --- a/common/content/sanitizer.js +++ b/common/content/sanitizer.js @@ -213,10 +213,9 @@ const Sanitizer = Module("sanitizer", { { setter: function (values) { for (let [, pref] in Iterator(sanitizer.prefNames)) { - continue; options.setPref(pref, false); - for (let [, value] in Iterator(this.parseValues(values))) { + for (let [, value] in Iterator(values)) { if (Sanitizer.prefToArg(pref) == value) { options.setPref(pref, true); break; @@ -226,7 +225,7 @@ const Sanitizer = Module("sanitizer", { return values; }, - getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(Sanitizer.prefToArg).join(","), + getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(Sanitizer.prefToArg), completer: function (value) [ ["cache", "Cache"], ["commandline", "Command-line history"], diff --git a/common/content/style.js b/common/content/style.js index 9183df53..dc206c5f 100644 --- a/common/content/style.js +++ b/common/content/style.js @@ -167,6 +167,8 @@ Highlights.prototype.CSS = <![CDATA[ HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top; HelpString::before content: '"'; HelpString::after content: '"'; + HelpString[delim]::before content: attr(delim); + HelpString[delim]::after content: attr(delim); HelpHead,html|h1,liberator://help/* { display: block; diff --git a/common/content/tabs.js b/common/content/tabs.js index 753202f7..283c9262 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -1094,9 +1094,9 @@ const Tabs = Module("tabs", { "Where to show requested popup windows", "stringlist", "tab", { - setter: function (value) { + setter: function (values) { let [open, restriction] = [1, 0]; - for (let [, opt] in Iterator(this.parseValues(value))) { + for (let [, opt] in Iterator(values)) { if (opt == "tab") open = 3; else if (opt == "window") @@ -1107,7 +1107,7 @@ const Tabs = Module("tabs", { options.safeSetPref("browser.link.open_newwindow", open, "See 'popups' option."); options.safeSetPref("browser.link.open_newwindow.restriction", restriction, "See 'popups' option."); - return value; + return values; }, completer: function (context) [ ["tab", "Open popups in a new tab"], diff --git a/common/content/template.js b/common/content/template.js index bd5edf76..26f0e512 100644 --- a/common/content/template.js +++ b/common/content/template.js @@ -194,8 +194,6 @@ const Template = Module("template", { return <>:{commandline.command}<br/>{xml}</>; }, - // every item must have a .xml property which defines how to draw itself - // @param headers is an array of strings, the text for the header columns genericTable: function genericTable(items, format) { completion.listCompleter(function (context) { context.filterFunc = null; diff --git a/common/locale/en-US/developer.xml b/common/locale/en-US/developer.xml index 3e7a8052..5bf793a7 100644 --- a/common/locale/en-US/developer.xml +++ b/common/locale/en-US/developer.xml @@ -179,7 +179,7 @@ </p> <code><![CDATA[ -var INFO = +var INFO = <plugin name="flashblock" version="1.0" href="http://ticket.vimperator.org/9" summary="Flash Blocker" diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index d4c7773f..e75c5847 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -17,11 +17,27 @@ </p> <dl> - <dt>boolean</dt> <dd>can only be on or off</dd> - <dt>number</dt> <dd>has a numeric value</dd> - <dt>string</dt> <dd>has a string value</dd> - <dt>charlist</dt> <dd>like a string but with unique characters</dd> - <dt>stringlist</dt> <dd>a comma-separated list of strings</dd> + <dt>boolean</dt> <dd>Can only be on or off</dd> + <dt>number</dt> <dd>A numeric value</dd> + <dt>string</dt> <dd>A string value</dd> + <dt>charlist</dt> <dd>A string containing a discrete set of distinct characters</dd> + <dt>stringlist</dt> <dd>A comma-separated list of strings</dd> + <dt>stringmap</dt> <dd>A comma-separated list of key-value pairs, e.g., <str>key=val,foo=bar</str></dd> + <dt>regexlist</dt> + <dd> + A comma-separated list of regular expressions. Expressions may be + prefixed with a <em>!</em>, in which case the match will be negated. A + literal <em>!</em> at the begining of the expression may be matched with + <em>[!]</em>. Generally, the first matching regular expression is used. + </dd> + <dt>regexmap</dt> + <dd> + A combination of a <em>stringmap</em> and a <em>regexlist</em>. Each key + in the <a>key</a>=<a>value</a> pair is a regexp. If the regexp begins with a + <em>!</em>, the sense match is negated, such that a non-matching + expression will be considered a match and <html:i>vice versa</html:i>. + The first <a>key</a> to match yields value. + </dd> </dl> <h2 tag="set-option E764">Setting options</h2> @@ -293,6 +309,40 @@ </description> </item> +<item> + <tags>'au' 'autocomplete'</tags> + <spec>'autocomplete' 'au'</spec> + <type>regexlist</type> + <default>.*</default> + <description> + <p> + A list of regexps defining which completion contexts should be + autocompleted. When the value is non-empty, the completion list is + automatically opened along with the commandline. Thereafter, any key + press triggers a completion update for the matching contexts. + Non-matching contexts will only be updated when the tab key is + pressed. This option is useful for disabling autocompletion for + computationally intense contexts that don't perform well on your + system under load. + </p> + + <example> + To enable autocompletion for everything but <ex>:history</ex> or + <ex>:bmarks</ex>, you would choose a value such as, + <str delim="'">!/ex/bmarks,.?</str> + </example> + + <note> + Completion contexts have names very much like Unix path names. This + denotes the tree in which they're called. A completer will never be + called unless every completer preceding it in the tree was also + called. For example, if your completer excludes <str>/ex/</str>, it + will also exclude <str>/ex/bmarks</str>, and so on. + </note> + + <p>See also <ex>:contexts</ex></p> + </description> +</item> <item> <tags>$CDPATH</tags> @@ -1249,41 +1299,50 @@ </description> </item> - <item> - <tags>'wildcase' 'wic'</tags> + <tags>'wic' 'wildcase'</tags> <spec>'wildcase' 'wic'</spec> - <type>string</type> + <type>regexmap</type> <default>smart</default> <description> - <p>Defines how completions are matched with regard to character case. Possible values:</p> + <p> + Defines how completions are matched for a given completion context + with regard to character case. + </p> + + <p>Possible values:</p> <dl> <dt><str>smart</str></dt> <dd>Case is significant when capital letters are typed</dd> <dt><str>match</str></dt> <dd>Case is always significant</dd> <dt><str>ignore</str></dt> <dd>Case is never significant</dd> </dl> + + <p>See also <ex>:contexts</ex></p> </description> </item> - <item> <tags>'wildignore' 'wig'</tags> <spec>'wildignore' 'wig'</spec> - <type>stringlist</type> + <type>regexlist</type> <default></default> <description> <p> List of file patterns to ignore when completing files. E.g., to ignore object files and Vim swap files - <ex>:set wildignore=<str>.<em>\\.o,\\..</em>\\.s[a-z]\\<a>2</a></str></ex> </p> + <code><ex>:set wildignore=<str delim="'">\.o$</str>,<str delim="'">^\..*\.s[a-z]<a>2</a>$</str></ex></code> + <note>Unlike Vim each pattern is a regex rather than a glob.</note> + <note> + The only way to include a literal comma in a pattern is with the + escape <str>\u0044</str>. + </note> </description> </item> - <item> <tags>'wim' 'wildmode'</tags> <spec>'wildmode' 'wim'</spec> @@ -1318,25 +1377,23 @@ </description> </item> - <item> - <tags>'wop' 'wildoptions'</tags> - <spec>'wildoptions' 'wop'</spec> - <type>stringlist</type> - <default></default> + <tags>'wis' 'wildsort'</tags> + <spec>'wildsort' 'wis'</spec> + <type>regexlist</type> + <default>.*</default> <description> - <p>A list of words that change how command-line completion is done.</p> - - <p>Possible words:</p> + <p> + A list of regexps defining which completion contexts + should be sorted. The main purpose of this option is to + prevent sorting of certain completion lists that don't + perform well under load. + </p> - <dl> - <dt>auto</dt> <dd>Automatically show completions while you are typing.</dd> - <dt>sort</dt> <dd>Always sort the completion list, overriding the <o>complete</o> option.</dd> - </dl> + <p>See also <ex>:contexts</ex></p> </description> </item> - <item> <tags>'wsp' 'wordseparators'</tags> <spec>'wordseparators' 'wsp'</spec> diff --git a/common/locale/en-US/various.xml b/common/locale/en-US/various.xml index 9fc05efb..7f7f7327 100644 --- a/common/locale/en-US/various.xml +++ b/common/locale/en-US/various.xml @@ -17,16 +17,27 @@ <tags>:beep</tags> <spec>:beep</spec> <description> - <p>Play a system beep.</p> + <p> + Play a system beep. This should not be used for any purpose other + than testing the visual bell. + </p> </description> </item> <item> - <tags><![CDATA[<C-l> CTRL-L :redr :redraw]]></tags> - <spec>:redr<oa>aw</oa></spec> + <tags>:contexts</tags> + <spec>:contexts <a>ex-command</a></spec> <description> - <p>Redraws the screen. Useful to update the screen halfway executing a script or function.</p> + <p> + Lists the completion contexts used during the completion of its + arguments. These context names are used in options such as + <o>autocomplete</o> and <o>wildcase</o>. Note that completion must + be triggered in order for this command to be effective, so if + autocompletion is not active, you'll need to press the + <k name="Tab"/> key at least once. You should also be aware that + this command is only useful from the commandline. + </p> </description> </item> @@ -47,6 +58,15 @@ <item> + <tags><![CDATA[<C-l> CTRL-L :redr :redraw]]></tags> + <spec>:redr<oa>aw</oa></spec> + <description> + <p>Redraws the screen. Useful to update the screen halfway executing a script or function.</p> + </description> +</item> + + +<item> <tags>:run :! :!cmd</tags> <spec>:!<a>cmd</a></spec> <description> |