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(
+
+ { template.completionRow(["Context", "Title"], "CompTitle") }
+ { template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
+
),
+ 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 @@
(default:
-
+
@@ -351,9 +351,13 @@
-
-
-
+
+
+
+ #
+
+
+
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 values 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 newValue for the specified scope.
@@ -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 name.
@@ -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;
+ let newValues = this._op(operator, values, scope, invert);
- 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;
-
- 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 values 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 = :{commandline.command}
{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 @@
- - boolean
- can only be on or off
- - number
- has a numeric value
- - string
- has a string value
- - charlist
- like a string but with unique characters
- - stringlist
- a comma-separated list of strings
+ - boolean
- Can only be on or off
+ - number
- A numeric value
+ - string
- A string value
+ - charlist
- A string containing a discrete set of distinct characters
+ - stringlist
- A comma-separated list of strings
+ - stringmap
- A comma-separated list of key-value pairs, e.g., key=val,foo=bar
+ - regexlist
+ -
+ A comma-separated list of regular expressions. Expressions may be
+ prefixed with a !, in which case the match will be negated. A
+ literal ! at the begining of the expression may be matched with
+ [!]. Generally, the first matching regular expression is used.
+
+ - regexmap
+ -
+ A combination of a stringmap and a regexlist. Each key
+ in the key=value pair is a regexp. If the regexp begins with a
+ !, the sense match is negated, such that a non-matching
+ expression will be considered a match and vice versa.
+ The first key to match yields value.
+
Setting options
@@ -293,6 +309,40 @@
+-
+ 'au' 'autocomplete'
+ 'autocomplete' 'au'
+ regexlist
+ .*
+
+
+ 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.
+
+
+
+ To enable autocompletion for everything but :history or
+ :bmarks, you would choose a value such as,
+ !/ex/bmarks,.?
+
+
+
+ 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 /ex/, it
+ will also exclude /ex/bmarks, and so on.
+
+
+ See also :contexts
+
+
-
$CDPATH
@@ -1249,41 +1299,50 @@
-
-
- 'wildcase' 'wic'
+ 'wic' 'wildcase'
'wildcase' 'wic'
- string
+ regexmap
smart
-
Defines how completions are matched with regard to character case. Possible values:
+
+ Defines how completions are matched for a given completion context
+ with regard to character case.
+
+
+ Possible values:
- smart
- Case is significant when capital letters are typed
- match
- Case is always significant
- ignore
- Case is never significant
+
+ See also :contexts
-
-
'wildignore' 'wig'
'wildignore' 'wig'
- stringlist
+ regexlist
List of file patterns to ignore when completing files. E.g., to ignore object
files and Vim swap files
- :set wildignore=.\\.o,\\..\\.s[a-z]\\2
+ :set wildignore=\.o$,^\..*\.s[a-z]2$
+
Unlike Vim each pattern is a regex rather than a glob.
+
+ The only way to include a literal comma in a pattern is with the
+ escape \u0044.
+
-
-
'wim' 'wildmode'
'wildmode' 'wim'
@@ -1318,25 +1377,23 @@
-
-
- 'wop' 'wildoptions'
- 'wildoptions' 'wop'
- stringlist
-
+ 'wis' 'wildsort'
+ 'wildsort' 'wis'
+ regexlist
+ .*
-
A list of words that change how command-line completion is done.
+
+ 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.
+
- Possible words:
-
-
- - auto
- Automatically show completions while you are typing.
- - sort
- Always sort the completion list, overriding the complete option.
-
+ See also :contexts
-
-
'wsp' 'wordseparators'
'wordseparators' 'wsp'
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 @@
:beep
:beep
-
Play a system beep.
+
+ Play a system beep. This should not be used for any purpose other
+ than testing the visual bell.
+
-
- CTRL-L :redr :redraw]]>
- :redraw
+ :contexts
+ :contexts ex-command
-
Redraws the screen. Useful to update the screen halfway executing a script or function.
+
+ Lists the completion contexts used during the completion of its
+ arguments. These context names are used in options such as
+ autocomplete and wildcase. 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
+ key at least once. You should also be aware that
+ this command is only useful from the commandline.
+
@@ -46,6 +57,15 @@
+-
+ CTRL-L :redr :redraw]]>
+ :redraw
+
+
Redraws the screen. Useful to update the screen halfway executing a script or function.
+
+
+
+
-
:run :! :!cmd
:!cmd