1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-21 11:47:59 +01:00

Better list option parsing and serialization.

This commit is contained in:
Kris Maglione
2010-09-24 15:24:21 -04:00
parent fa20629524
commit 0753c9505e
8 changed files with 121 additions and 66 deletions

View File

@@ -1071,6 +1071,7 @@ const CommandLine = Module("commandline", {
*/ */
select: function (backward, matchCurrent) { select: function (backward, matchCurrent) {
// always reset the tab completion if we use up/down keys // always reset the tab completion if we use up/down keys
if (commandline._completions)
commandline._completions.reset(); commandline._completions.reset();
let diff = backward ? -1 : 1; let diff = backward ? -1 : 1;

View File

@@ -189,7 +189,15 @@ const Command = Class("Command", {
* @returns {Args} * @returns {Args}
* @see Commands#parseArgs * @see Commands#parseArgs
*/ */
parseArgs: function (args, complete, extra) commands.parseArgs(args, this.options, this.argCount, false, this.literal, complete, extra), parseArgs: function (args, complete, extra) commands.parseArgs(args, {
allowUnknownOptions: !!this.allowUnknownOptions,
argCount: this.argCount,
complete: complete,
extra: extra,
keepQuotes: !!this.keepQuotes,
literal: this.literal,
options: this.options
}),
/** /**
* @property {string[]} All of this command's name specs. e.g., "com[mand]" * @property {string[]} All of this command's name specs. e.g., "com[mand]"
@@ -405,16 +413,15 @@ const Commands = Module("commands", {
*/ */
commandToString: function (args) { commandToString: function (args) {
let res = [args.command + (args.bang ? "!" : "")]; let res = [args.command + (args.bang ? "!" : "")];
function quote(str) Commands.quoteArg[/[\s"'\\]|^$/.test(str) ? "'" : ""](str);
for (let [opt, val] in Iterator(args.options || {})) { for (let [opt, val] in Iterator(args.options || {})) {
let chr = /^-.$/.test(opt) ? " " : "="; let chr = /^-.$/.test(opt) ? " " : "=";
if (val != null) if (val != null)
opt += chr + quote(val); opt += chr + Commands.quote(val);
res.push(opt); res.push(opt);
} }
for (let [, arg] in Iterator(args.arguments || [])) for (let [, arg] in Iterator(args.arguments || []))
res.push(quote(arg)); res.push(Commands.quote(arg));
let str = args.literalArg; let str = args.literalArg;
if (str) if (str)
@@ -556,7 +563,7 @@ const Commands = Module("commands", {
*/ */
parseArgs: function (str, options, argCount, allowUnknownOptions, literal, complete, extra) { parseArgs: function (str, options, argCount, allowUnknownOptions, literal, complete, extra) {
function getNextArg(str) { function getNextArg(str) {
let [count, arg, quote] = Commands.parseArg(str); let [count, arg, quote] = Commands.parseArg(str, null, keepQuotes);
if (quote == "\\" && !complete) if (quote == "\\" && !complete)
return [,,,"Trailing \\"]; return [,,,"Trailing \\"];
if (quote && !complete) if (quote && !complete)
@@ -564,6 +571,11 @@ const Commands = Module("commands", {
return [count, arg, quote]; return [count, arg, quote];
} }
let keepQuotes;
if (isObject(options))
({ allowUnknownOptions, argCount, complete, extra, literal, options, keepQuotes }) = options;
if (!options) if (!options)
options = []; options = [];
@@ -581,7 +593,7 @@ const Commands = Module("commands", {
var invalid = false; var invalid = false;
// FIXME: best way to specify these requirements? // FIXME: best way to specify these requirements?
var onlyArgumentsRemaining = allowUnknownOptions || options.length == 0 || false; // after a -- has been found var onlyArgumentsRemaining = allowUnknownOptions || options.length == 0; // after a -- has been found
var arg = null; var arg = null;
var count = 0; // the length of the argument var count = 0; // the length of the argument
var i = 0; var i = 0;
@@ -866,19 +878,26 @@ const Commands = Module("commands", {
} }
}, { }, {
// returns [count, parsed_argument] // returns [count, parsed_argument]
parseArg: function (str) { parseArg: function (str, sep, keepQuotes) {
let arg = ""; let arg = "";
let quote = null; let quote = null;
let len = str.length; let len = str.length;
while (str.length && !/^\s/.test(str)) { // Fix me.
if (isString(sep))
sep = RegExp(sep);
sep = sep != null ? sep : /\s/;
let re1 = RegExp("^" + (sep.source === "" ? "(?!)" : sep.source));
let re2 = RegExp(/^()((?:[^\\S"']|\\.)+)((?:\\$)?)/.source.replace("S", sep.source));
while (str.length && !re1.test(str)) {
let res; let res;
if ((res = str.match = str.match(/^()((?:[^\\\s"']|\\.)+)((?:\\$)?)/))) if ((res = re2.exec(str)))
arg += res[2].replace(/\\(.)/g, "$1"); arg += keepQuotes ? res[0] : res[2].replace(/\\(.)/g, "$1");
else if ((res = str.match(/^(")((?:[^\\"]|\\.)*)("?)/))) else if ((res = /^(")((?:[^\\"]|\\.)*)("?)/.exec(str)))
arg += eval(res[0] + (res[3] ? "" : '"')); arg += keepQuotes ? res[0] : eval(res[0] + (res[3] ? "" : '"'));
else if ((res = str.match(/^(')((?:[^']|'')*)('?)/))) else if ((res = /^(')((?:[^']|'')*)('?)/.exec(str)))
arg += res[2].replace("''", "'", "g"); arg += keepQuotes ? res[0] : res[2].replace("''", "'", "g");
else else
break; break;
@@ -890,7 +909,9 @@ const Commands = Module("commands", {
} }
return [len - str.length, arg, quote]; return [len - str.length, arg, quote];
} },
quote: function quote(str) Commands.quoteArg[/[\s"'\\]|^$/.test(str) ? "'" : ""](str)
}, { }, {
completion: function () { completion: function () {
completion.command = function command(context) { completion.command = function command(context) {
@@ -1155,8 +1176,9 @@ const Commands = Module("commands", {
(function () { (function () {
Commands.quoteMap = { Commands.quoteMap = {
"\n": "n", "\n": "\\n",
"\t": "t" "\t": "\\t",
"'": "''"
}; };
function quote(q, list) { function quote(q, list) {
let re = RegExp("[" + list + "]", "g"); let re = RegExp("[" + list + "]", "g");
@@ -1166,14 +1188,14 @@ const Commands = Module("commands", {
}; };
Commands.complQuote = { Commands.complQuote = {
'"': ['"', quote("", '\n\t"\\\\'), '"'], '"': ['"', quote("", '\n\t"\\\\'), '"'],
"'": ["'", quote("", "\\\\'"), "'"], "'": ["'", quote("", "'"), "'"],
"": ["", quote("", "\\\\ '\""), ""] "": ["", quote("", "\\\\ '\""), ""]
}; };
Commands.quoteArg = { Commands.quoteArg = {
'"': quote('"', '\n\t"\\\\'), '"': quote('"', '\n\t"\\\\'),
"'": quote("'", "\\\\'"), "'": quote("'", "'"),
"": quote("", "\\\\ '\"") "": quote("", "\\\\\\s'\"")
}; };
Commands.parseBool = function (arg) { Commands.parseBool = function (arg) {

View File

@@ -1291,7 +1291,7 @@ const Dactyl = Module("dactyl", {
options.add(["loadplugins", "lpl"], options.add(["loadplugins", "lpl"],
"A regex list that defines which plugins are loaded at startup and via :loadplugins", "A regex list that defines which plugins are loaded at startup and via :loadplugins",
"regexlist", "\\.(js|vimp)$"); "regexlist", "'\\.(js|vimp)$'");
options.add(["titlestring"], options.add(["titlestring"],
"Change the title of the window", "Change the title of the window",

View File

@@ -211,7 +211,7 @@ const RangeFinder = Module("rangefinder", {
options.add(["hlsearch", "hls"], options.add(["hlsearch", "hls"],
"Highlight previous search pattern matches", "Highlight previous search pattern matches",
"boolean", "false", { "boolean", false, {
setter: function (value) { setter: function (value) {
try { try {
if (value) if (value)

View File

@@ -50,8 +50,11 @@ const Option = Class("Option", {
this._op = Option.ops[this.type]; this._op = Option.ops[this.type];
if (arguments.length > 3) if (arguments.length > 3) {
this.defaultValue = defaultValue; if (this.type == "string")
defaultValue = Commands.quote(defaultValue);
this.defaultValue = this.joinValues(this.parseValues(defaultValue));
}
if (extraInfo) if (extraInfo)
update(this, extraInfo); update(this, extraInfo);
@@ -75,7 +78,7 @@ const Option = Class("Option", {
* @param {value} value The option value. * @param {value} value The option value.
* @returns {value|string[]} * @returns {value|string[]}
*/ */
parseValues: function (value) value, parseValues: function (value) Option.dequote(value),
/** /**
* Returns <b>values</b> packed in the appropriate format for the option * Returns <b>values</b> packed in the appropriate format for the option
@@ -84,7 +87,7 @@ const Option = Class("Option", {
* @param {value|string[]} values The option value. * @param {value|string[]} values The option value.
* @returns {value} * @returns {value}
*/ */
joinValues: function (vals) vals, joinValues: function (vals) Commands.quote(vals),
/** @property {value|string[]} The option value or array of values. */ /** @property {value|string[]} The option value or array of values. */
get values() this.getValues(this.scope), get values() this.getValues(this.scope),
@@ -376,8 +379,8 @@ const Option = Class("Option", {
re.toString = function () Option.unparseRegex(this); re.toString = function () Option.unparseRegex(this);
return re; return re;
}, },
unparseRegex: function (re) re.bang + re.source.replace(/\\(.)/g, function (m, n1) n1 == "/" ? n1 : m) + unparseRegex: function (re) re.bang + Option.quote(re.source.replace(/\\(.)/g, function (m, n1) n1 == "/" ? n1 : m)) +
(typeof re.result == "string" ? ":" + re.result : ""), (typeof re.result == "string" ? ":" + Option.quote(re.result) : ""),
getKey: { getKey: {
stringlist: function (k) this.values.indexOf(k) >= 0, stringlist: function (k) this.values.indexOf(k) >= 0,
@@ -393,24 +396,46 @@ const Option = Class("Option", {
}, },
joinValues: { joinValues: {
charlist: function (vals) vals.join(""), charlist: function (vals) Commands.quote(vals.join("")),
stringlist: function (vals) vals.join(","), stringlist: function (vals) vals.map(Option.quote).join(","),
stringmap: function (vals) [k + ":" + v for ([k, v] in Iterator(vals))].join(","), stringmap: function (vals) [Option.quote(k) + ":" + Option.quote(v) for ([k, v] in Iterator(vals))].join(","),
regexlist: function (vals) vals.map(Option.unparseRegex).join(","), regexlist: function (vals) vals.join(","),
get regexmap() this.regexlist get regexmap() this.regexlist
}, },
parseValues: { parseValues: {
number: function (value) Number(value), number: function (value) Number(Option.dequote(value)),
boolean: function (value) value == "true" || value == true ? true : false, boolean: function (value) Option.dequote(value) == "true" || value == true ? true : false,
charlist: function (value) Array.slice(value), charlist: function (value) Array.slice(Option.dequote(value)),
stringlist: function (value) (value === "") ? [] : value.split(","), stringlist: function (value) (value === "") ? [] : Option.splitList(value),
stringmap: function (value) array(util.split(v, /:/g, 2) for (v in values(value.split(",")))).toObject(), stringmap: function (value) array(util.split(v, /:/g, 2) for (v in values(Option.splitList(value)))).toObject(),
regexlist: function (value) (value === "") ? [] : value.split(",").map(Option.parseRegex), regexlist: function (value) (value === "") ? [] : Option.splitList(value).map(Option.parseRegex),
regexmap: function (value) value.split(",").map(function (v) util.split(v, /:/g, 2)) regexmap: function (value) Option.splitList(value)
.map(function (v) util.split(v, /:/g, 2))
.map(function ([k, v]) v != null ? Option.parseRegex(k, v) : Option.parseRegex(".?", k)) .map(function ([k, v]) v != null ? Option.parseRegex(k, v) : Option.parseRegex(".?", k))
}, },
dequote: function (value) {
let arg;
[, arg, Option._quote] = Commands.parseArg(String(value), "");
Option._splitAt = 0;
return arg;
},
splitList: function (value) {
let res = [];
Option._splitAt = 0;
do {
if (count !== undefined)
Option._splitAt += count + 1;
var [count, arg, quote] = Commands.parseArg(value, /,/);
Option._quote = quote; // FIXME
res.push(arg);
value = value.slice(count + 1);
} while (value.length);
return res;
},
quote: function quote(str) Commands.quoteArg[/[\s"'\\,]|^$/.test(str) ? "'" : ""](str),
ops: { ops: {
boolean: function (operator, values, scope, invert) { boolean: function (operator, values, scope, invert) {
if (operator != "=") if (operator != "=")
@@ -640,7 +665,7 @@ const Options = Module("options", {
if (!scope) if (!scope)
scope = Option.SCOPE_BOTH; scope = Option.SCOPE_BOTH;
if (name in this._optionMap && (this._optionMap[name].scope & scope)) if (this._optionMap[name] && (this._optionMap[name].scope & scope))
return this._optionMap[name]; return this._optionMap[name];
return null; return null;
}, },
@@ -661,7 +686,7 @@ const Options = Module("options", {
function opts(opt) { function opts(opt) {
for (let opt in Iterator(options)) { for (let opt in Iterator(options)) {
let option = { let option = {
isDefault: opt.value == opt.defaultValue, isDefault: opt.value === opt.defaultValue,
name: opt.name, name: opt.name,
default: opt.defaultValue, default: opt.defaultValue,
pre: "\u00a0\u00a0", // Unicode nonbreaking space. pre: "\u00a0\u00a0", // Unicode nonbreaking space.
@@ -1261,7 +1286,7 @@ const Options = Module("options", {
serialize: function () [ serialize: function () [
{ {
command: this.name, command: this.name,
arguments: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name literalArg: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name
: opt.name + "=" + opt.value] : opt.name + "=" + opt.value]
} }
for (opt in options) for (opt in options)
@@ -1276,6 +1301,9 @@ const Options = Module("options", {
}, },
update({ update({
bang: true, bang: true,
completer: function (context, args) {
return setCompleter(context, args);
},
domains: function (args) array.flatten(args.map(function (spec) { domains: function (args) array.flatten(args.map(function (spec) {
try { try {
let opt = options.parseOpt(spec); let opt = options.parseOpt(spec);
@@ -1287,9 +1315,7 @@ const Options = Module("options", {
} }
return []; return [];
})), })),
completer: function (context, args) { keepQuotes: true,
return setCompleter(context, args);
},
privateData: function (args) args.some(function (spec) { privateData: function (args) args.some(function (spec) {
let opt = options.parseOpt(spec); let opt = options.parseOpt(spec);
return opt.option && opt.option.privateData && return opt.option && opt.option.privateData &&
@@ -1347,33 +1373,30 @@ const Options = Module("options", {
return; return;
} }
let len = context.filter.length;
switch (opt.type) { switch (opt.type) {
case "boolean": case "boolean":
if (!completer) if (!completer)
completer = function () [["true", ""], ["false", ""]]; completer = function () [["true", ""], ["false", ""]];
break; break;
case "regexlist": case "regexlist":
newValues = context.filter.split(","); newValues = Option.splitList(context.filter);
// Fallthrough // Fallthrough
case "stringlist": case "stringlist":
let target = newValues.pop() || ""; var target = newValues.pop() || "";
len = target.length;
break; break;
case "stringmap": case "stringmap":
case "regexmap": case "regexmap":
let vals = context.filter.split(","); let vals = Option.splitList(context.filter);
target = vals.pop() || ""; target = vals.pop() || "";
len = target.length - (target.indexOf(":") + 1); Option._splitAt += target.indexOf(":") ? target.indexOf(":") + 1 : 0;
break;
case "charlist":
len = 0;
break; break;
} }
// TODO: Highlight when invalid // TODO: Highlight when invalid
context.advance(context.filter.length - len); context.advance(Option._splitAt);
context.filter = target != null ? target : Option.dequote(context.filter);
context.title = ["Option Value"]; context.title = ["Option Value"];
context.quote = Commands.complQuote[Option._quote] || Commands.complQuote[""]
// Not Vim compatible, but is a significant enough improvement // Not Vim compatible, but is a significant enough improvement
// that it's worth breaking compatibility. // that it's worth breaking compatibility.
if (isArray(newValues)) { if (isArray(newValues)) {

View File

@@ -30,14 +30,21 @@
<dt>number</dt> <dd>A numeric value</dd> <dt>number</dt> <dd>A numeric value</dd>
<dt>string</dt> <dd>A string value</dd> <dt>string</dt> <dd>A string value</dd>
<dt>charlist</dt> <dd tag="charlist">A string containing a discrete set of distinct characters</dd> <dt>charlist</dt> <dd tag="charlist">A string containing a discrete set of distinct characters</dd>
<dt>stringlist</dt> <dd tag="stringlist">A comma-separated list of strings</dd> <dt>stringlist</dt>
<dd tag="stringlist">
A comma-separated list of strings. Any comma appearing within single
or double quotes, or prefixed with a <em>\</em>, will not be treated
as an item separator.
</dd>
<dt>stringmap</dt> <dd tag="stringmap">A comma-separated list of key-value pairs, e.g., <str>key:val,foo:bar</str></dd> <dt>stringmap</dt> <dd tag="stringmap">A comma-separated list of key-value pairs, e.g., <str>key:val,foo:bar</str></dd>
<dt>regexlist</dt> <dt>regexlist</dt>
<dd tag="regexlist"> <dd tag="regexlist">
A comma-separated list of regular expressions. Expressions may be A comma-separated list of regular expressions. Expressions may be
prefixed with a <em>!</em>, in which case the match will be negated. A 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 literal <em>!</em> at the begining of the expression may be matched with
<em>[!]</em>. Generally, the first matching regular expression is used. <em>[!]</em>. Generally, the first matching regular expression is
used. Any comma appearing within single or double quotes, or prefixed
with a <em>\</em>, will not be treated as an item separator.
</dd> </dd>
<dt>regexmap</dt> <dt>regexmap</dt>
<dd tag="regexmap"> <dd tag="regexmap">
@@ -1452,10 +1459,6 @@
<code><ex>:set wildignore=<str delim="'">\.o$</str>,<str delim="'">^\..*\.s[a-z]<a>2</a>$</str></ex></code> <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 regular expression rather than a glob.</note> <note>Unlike Vim, each pattern is a regular expression rather than a glob.</note>
<note>
The only way to include a literal comma in a pattern is with the
escape sequence <str>\u0044</str>.
</note>
</description> </description>
</item> </item>

View File

@@ -474,8 +474,9 @@ function call(fn) {
* value of the property. * value of the property.
*/ */
function memoize(obj, key, getter) { function memoize(obj, key, getter) {
obj.__defineGetter__(key, function () obj.__defineGetter__(key, function () (
Class.replaceProperty(this, key, getter.call(this, key))); Class.replaceProperty(this, key, null),
Class.replaceProperty(this, key, getter.call(this, key))));
} }
/** /**

View File

@@ -26,6 +26,11 @@
I.e., 'fo\o''bar' ≡ fo\o'bar I.e., 'fo\o''bar' ≡ fo\o'bar
* IMPORTANT: 'cdpath' and 'runtimepath' no longer treat ,, * IMPORTANT: 'cdpath' and 'runtimepath' no longer treat ,,
specially. Use . instead. specially. Use . instead.
* IMPORTANT: Option value quoting has changed. List options will
no longer be split at quoted commas and the option name,
operators, and = sign may no longer be quoted. This will break
certain automatically-generated configuration files.
See :help stringlist
* Added 'altwildmode' and <A-Tab> commandline key binding. * Added 'altwildmode' and <A-Tab> commandline key binding.
* Added 'autocomplete' option for specifying which completion * Added 'autocomplete' option for specifying which completion
groups should be auto-completed. groups should be auto-completed.