diff --git a/common/content/buffer.js b/common/content/buffer.js index ad4ce71b..46fb9988 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -961,7 +961,7 @@ const Buffer = Module("buffer", { let doc = buffer.focusedFrame.document; if (isArray(url)) { - if (options.get("editor").has("l")) + if (options.get("editor").has("line")) this.viewSourceExternally(url[0] || doc, url[1]); else window.openDialog("chrome://global/content/viewSource.xul", diff --git a/common/content/editor.js b/common/content/editor.js index 61bcf06e..c79c89c5 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -242,7 +242,7 @@ const Editor = Module("editor", { }, editFileExternally: function (path, line, column) { - let args = options.get("editor").format({ f: path, l: line, c: column }); + let args = options.get("editor").format({ file: path, line: line, column: column }); dactyl.assert(args.length >= 1, "No editor specified"); @@ -772,19 +772,19 @@ const Editor = Module("editor", { options: function () { options.add(["editor"], "The external text editor", - "string", "gvim -f +%l %f", { + "string", "gvim -f + ", { format: function (obj, value) { let args = commands.parseArgs(value || this.value, { argCount: "*", allowUnknownOptions: true }) - .map(util.compileFormat).filter(function (fmt) fmt.valid(obj)) + .map(util.compileMacro).filter(function (fmt) fmt.valid(obj)) .map(function (fmt) fmt(obj)); - if (obj["f"] && !this.has("f")) - args.push(obj["f"]); + if (obj["file"] && !this.has("file")) + args.push(obj["file"]); return args; }, - has: function (key) set.has(util.compileFormat(this.value).seen, key), + has: function (key) set.has(util.compileMacro(this.value).seen, key), validator: function (value) { this.format({}, value); - return Object.keys(util.compileFormat(value).seen).every(function (k) "cfl".indexOf(k) >= 0) + return Object.keys(util.compileMacro(value).seen).every(function (k) ["column", "file", "line"].indexOf(k) >= 0) } }); diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index d9d24776..e99d475f 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -88,6 +88,30 @@ line: 32 'Lieder eines fahrenden Gesellen.txt'

+

+ Some options may be given format strings containing macro replacements in + the form of <name>. These tokens are replaced by + the parameter name as specified in the relevant documentation. + If the token is in the form <q-name>, the value of the + parameter is automatically quoted. +

+

+ Any substring enclosed by <[ + and ]> is automatically elided if any of the contained macros + characters aren't currently valid. A literal < or > + character may be included with the special escape sequences <lt> + or <gt> respectively. +

+ +

+ For example, given the format string + <[(cmd: <column>) ]><[line: <line> ]><file>, + where line=32 and + file=Lieder eines fahrenden Gesellen.txt, + the result is formatted as + line: 32 'Lieder eines fahrenden Gesellen.txt' +

+

Setting options

@@ -553,7 +577,7 @@ 'editor' 'editor' string - gvim -f + ]]>

Set the external text editor. @@ -562,16 +586,16 @@

- Accepts a format-string with the following escapes available. + Accepts a macro-string with the following escapes available. Arguments containing escapes which are not relevant to a given call are automatically elided. All field splitting is done before format characters are processed.

-
%f
The file to edit. Appended as the final argument if missing.
-
%l
The line number at which to position the cursor.
-
%c
The column at which to position the cursor.
+
<file>
The file to edit. Appended as the final argument if missing.
+
<line>
The line number at which to position the cursor.
+
<column>
The column at which to position the cursor.
diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 293ffb37..0e8c7734 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -244,6 +244,73 @@ const Util = Module("Util", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]) return stack.top; }, + compileMacro: function compileFormat(macro) { + let stack = [frame()]; + stack.__defineGetter__("top", function () this[this.length - 1]); + + function frame() update( + function _frame(obj) + _frame === stack.top || _frame.valid(obj) ? + _frame.elements.map(function (e) callable(e) ? e(obj) : e).join("") : "", + { + elements: [], + seen: {}, + valid: function (obj) this.elements.every(function (e) !e.test || e.test(obj)) + }); + + let defaults = { lt: "<", gt: ">" }; + + let match, end = 0; + let re = util.regexp( | // 3 + (\]>) // 4 + ) + ]]>, "gy"); + while (match = re.exec(macro)) { + let [, prefix, open, macro, close] = match; + end += match[0].length; + + if (prefix) + stack.top.elements.push(prefix); + if (open) { + let f = frame(); + stack.top.elements.push(f); + stack.push(f); + } + else if (close) { + stack.pop(); + util.assert(stack.length, "Unmatched %] in macro"); + } + else { + let [, flags, name] = /^((?:[a-z]-)*)(.*)/.exec(macro); + flags = set(flags); + + let quote = util.identity; + if (flags.q) + quote = function quote(obj) typeof obj === "number" ? obj : Commands.quote(obj); + + if (set.has(defaults, name)) + stack.top.elements.push(quote(defaults[name])); + else { + stack.top.elements.push(update( + function (obj) obj[name] != null ? quote(obj[name]) : "", + { test: function (obj) obj[name] != null })); + + for (let elem in array.iterValues(stack)) + elem.seen[name] = true; + } + } + } + if (end < macro.length) + stack.top.elements.push(macro.substr(end)); + + util.assert(stack.length === 1, "Unmatched <[ in macro"); + return stack.top; + }, + /** * Returns an object representing a Node's computed CSS style. *