summaryrefslogtreecommitdiff
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/content/bookmarks.js12
-rw-r--r--common/content/commandline.js80
-rw-r--r--common/content/commands.js4
-rw-r--r--common/content/completion.js93
-rw-r--r--common/content/help.xsl12
-rw-r--r--common/content/io.js10
-rw-r--r--common/content/options.js405
-rw-r--r--common/content/sanitizer.js5
-rw-r--r--common/content/style.js2
-rw-r--r--common/content/tabs.js6
-rw-r--r--common/content/template.js2
-rw-r--r--common/locale/en-US/developer.xml2
-rw-r--r--common/locale/en-US/options.xml109
-rw-r--r--common/locale/en-US/various.xml28
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>