// Copyright (c) 2008-2011 by Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. "use strict"; Components.utils.import("resource://dactyl/bootstrap.jsm"); defineModule("template", { exports: ["Binding", "Template", "template"], require: ["util"], use: ["messages", "services"] }, this); default xml namespace = XHTML; var Binding = Class("Binding", { init: function (node, nodes) { this.node = node; this.nodes = nodes; node.dactylBinding = this; Object.defineProperties(node, this.constructor.properties); for (let [event, handler] in values(this.constructor.events)) node.addEventListener(event, handler, false); }, set collapsed(collapsed) { if (collapsed) this.setAttribute("collapsed", "true"); else this.removeAttribute("collapsed"); }, get collapsed() !!this.getAttribute("collapsed"), __noSuchMethod__: Class.Property({ configurable: true, writeable: true, value: function __noSuchMethod__(meth, args) { return this.node[meth].apply(this.node, args); } }) }, { get bindings() { let bindingProto = Object.getPrototypeOf(Binding.prototype); for (let obj = this.prototype; obj !== bindingProto; obj = Object.getPrototypeOf(obj)) yield obj; }, bind: function bind(func) function bound() { try { return func.apply(this.dactylBinding, arguments); } catch (e) { util.reportError(e); throw e; } }, events: Class.memoize(function () { let res = []; for (let obj in this.bindings) if (Object.getOwnPropertyDescriptor(obj, "events")) for (let [event, handler] in Iterator(obj.events)) res.push([event, this.bind(handler)]); return res; }), properties: Class.memoize(function () { let res = {}; for (let obj in this.bindings) for (let prop in properties(obj)) { let desc = Object.getOwnPropertyDescriptor(obj, prop); if (desc.enumerable) { for (let k in values(["get", "set", "value"])) if (typeof desc[k] === "function") desc[k] = this.bind(desc[k]); res[prop] = desc; } } return res; }) }); var Template = Module("Template", { add: function add(a, b) a + b, join: function join(c) function (a, b) a + c + b, map: function map(iter, func, sep, interruptable) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; if (iter.length) // FIXME: Kludge? iter = array.iterValues(iter); let res = <>; let n = 0; for each (let i in Iterator(iter)) { let val = func(i, n); if (val == undefined) continue; if (n++ && sep) res += sep; if (interruptable && n % interruptable == 0) util.threadYield(true, true); res += val; } return res; }, bindings: { Button: Class("Button", Binding, { init: function init(node, params) { init.supercall(this, node); this.target = params.commandTarget; }, get command() this.getAttribute("command") || this.getAttribute("key"), events: { "click": function onClick(event) { event.preventDefault(); if (this.commandAllowed) { if (Set.has(this.target.commands || {}, this.command)) this.target.commands[this.command].call(this.target); else this.target.command(this.command); } } }, get commandAllowed() { if (Set.has(this.target.allowedCommands || {}, this.command)) return this.target.allowedCommands[this.command]; if ("commandAllowed" in this.target) return this.target.commandAllowed(this.command); return true; }, update: function update() { let collapsed = this.collapsed; this.collapsed = !this.commandAllowed; if (collapsed == this.commandAllowed) { let event = this.node.ownerDocument.createEvent("Events"); event.initEvent("dactyl-commandupdate", true, false); this.node.ownerDocument.dispatchEvent(event); } } }), Events: Class("Events", Binding, { init: function init(node, params) { init.supercall(this, node); let obj = params.eventTarget; let events = obj[this.getAttribute("events") || "events"]; for (let [event, handler] in Iterator(events)) node.addEventListener(event, obj.closure(handler), false); } }) }, bookmarkDescription: function (item, text) <> { !(item.extra && item.extra.length) ? "" : ({ template.map(item.extra, function (e) <>{e[0]}: {e[1]}, <> ) })  } {text || ""} , filter: function (str) {str}, completionRow: function completionRow(item, highlightGroup) { if (typeof icon == "function") icon = icon(); if (highlightGroup) { var text = item[0] || ""; var desc = item[1] || ""; } else { var text = this.processor[0].call(this, item, item.result); var desc = this.processor[1].call(this, item, item.description); } XML.ignoreWhitespace = false; XML.prettyPrinting = false; // return
  • {text} 
  • {desc} 
  • ; //
    }, helpLink: function (token, text, type) { if (!services["dactyl:"].initialized) util.dactyl.initHelp(); let topic = token; // FIXME: Evil duplication! if (/^\[.*\]$/.test(topic)) topic = topic.slice(1, -1); else if (/^n_/.test(topic)) topic = topic.slice(2); if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic)) return {text || token}; XML.ignoreWhitespace = false; XML.prettyPrinting = false; type = type || (/^'.*'$/.test(token) ? "HelpOpt" : /^\[.*\]$|^E\d{3}$/.test(token) ? "HelpTopic" : /^:\w/.test(token) ? "HelpEx" : "HelpKey"); return {text || topic}; }, HelpLink: function (token) { if (!services["dactyl:"].initialized) util.dactyl.initHelp(); let topic = token; // FIXME: Evil duplication! if (/^\[.*\]$/.test(topic)) topic = topic.slice(1, -1); else if (/^n_/.test(topic)) topic = topic.slice(2); if (services["dactyl:"].initialized && !Set.has(services["dactyl:"].HELP_TAGS, topic)) return <>{token}; XML.ignoreWhitespace = false; XML.prettyPrinting = false; let tag = (/^'.*'$/.test(token) ? "o" : /^\[.*\]$|^E\d{3}$/.test(token) ? "t" : /^:\w/.test(token) ? "ex" : "k"); topic = topic.replace(/^'(.*)'$/, "$1"); return <{tag} xmlns={NS}>{topic}; }, linkifyHelp: function linkifyHelp(str, help) { let re = util.regexp( [/\s]|^) (?P '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:\w+|.) | \[[\w-]+\] | E\d{3} ) (?= [[\)!,:;./\s]|$) ]]>, "gx"); return this.highlightSubstrings(str, (function () { for (let res in re.iterate(str)) yield [res.index + res.pre.length, res.tag.length]; })(), template[help ? "HelpLink" : "helpLink"]); }, // if "processStrings" is true, any passed strings will be surrounded by " and // any line breaks are displayed as \n highlight: function highlight(arg, processStrings, clip) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; // some objects like window.JSON or getBrowsers()._browsers need the try/catch try { let str = clip ? util.clip(String(arg), clip) : String(arg); switch (arg == null ? "undefined" : typeof arg) { case "number": return {str}; case "string": if (processStrings) str = str.quote(); return {str}; case "boolean": return {str}; case "function": // Vim generally doesn't like /foo*/, because */ looks like a comment terminator. // Using /foo*(:?)/ instead. if (processStrings) return {str.replace(/\{(.|\n)*(?:)/g, "{ ... }")}; <>}; /* Vim */ return <>{arg}; case "undefined": return {arg}; case "object": if (arg instanceof Ci.nsIDOMElement) return util.objectToString(arg, false); // for java packages value.toString() would crash so badly // that we cannot even try/catch it if (/^\[JavaPackage.*\]$/.test(arg)) return <>[JavaPackage]; if (processStrings && false) str = template.highlightFilter(str, "\n", function () ^J); return {str}; case "xml": return arg; default: return ]]>; } } catch (e) { return ]]>; } }, highlightFilter: function highlightFilter(str, filter, highlight) { return this.highlightSubstrings(str, (function () { if (filter.length == 0) return; let lcstr = String.toLowerCase(str); let lcfilter = filter.toLowerCase(); let start = 0; while ((start = lcstr.indexOf(lcfilter, start)) > -1) { yield [start, filter.length]; start += filter.length; } })(), highlight || template.filter); }, highlightRegexp: function highlightRegexp(str, re, highlight) { return this.highlightSubstrings(str, (function () { for (let res in util.regexp.iterate(re, str)) yield [res.index, res[0].length, res.wholeMatch ? [res] : res]; })(), highlight || template.filter); }, highlightSubstrings: function highlightSubstrings(str, iter, highlight) { XML.ignoreWhitespace = XML.prettyPrinting = false; if (typeof str == "xml") return str; if (str == "") return <>{str}; str = String(str).replace(" ", "\u00a0"); let s = <>; let start = 0; let n = 0, _i; for (let [i, length, args] in iter) { if (i == _i || i < _i) break; _i = i; XML.ignoreWhitespace = false; s += <>{str.substring(start, i)}; s += highlight.apply(this, Array.concat(args || str.substr(i, length))); start = i + length; } return s + <>{str.substr(start)}; }, highlightURL: function highlightURL(str, force) { if (force || /^[a-zA-Z]+:\/\//.test(str)) return {util.losslessDecodeURI(str)}; else return str; }, icon: function (item, text) <> {item.icon ? : <>}{text} , jumps: function jumps(index, elems) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; // return { this.map(Iterator(elems), function ([idx, val]) ) }
    {_("title.Jump")} {_("title.Title")} {_("title.URI")}
    {idx == index ? ">" : ""} {Math.abs(idx - index)} {val.title} {util.losslessDecodeURI(val.URI.spec)}
    ; //
    }, options: function options(title, opts, verbose) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; // return { this.map(opts, function (opt) ) }
    --- {title} ---
    {opt.pre}{opt.name}{opt.value}{ opt.isDefault || opt.default == null ? "" : (default: {opt.default}) }
    { verbose && opt.setFrom ?
    Last set from {template.sourceLink(opt.setFrom)}
    : <> }
    ; //
    }, sourceLink: function (frame) { let url = util.fixURI(frame.filename || "unknown"); let path = util.urlPath(url); XML.ignoreWhitespace = false; XML.prettyPrinting = false; return { path + ":" + frame.lineNumber }; }, table: function table(title, data, indent) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; let table = // { this.map(data, function (datum) ) }
    {title}
    {datum[0]} {datum[1]}
    ; //
    if (table.tr.length() > 1) return table; }, tabular: function tabular(headings, style, iter) { // TODO: This might be mind-bogglingly slow. We'll see. XML.ignoreWhitespace = false; XML.prettyPrinting = false; // return { this.map(headings, function (h) ) } { this.map(iter, function (row) { template.map(Iterator(row), function ([i, d]) ) } ) }
    {h}
    {d}
    ; //
    }, usage: function usage(iter, format) { XML.ignoreWhitespace = false; XML.prettyPrinting = false; format = format || {}; let desc = format.description || function (item) template.linkifyHelp(item.description); let help = format.help || function (item) item.name; function sourceLink(frame) { let source = template.sourceLink(frame); source.@NS::hint = source.text(); return source; } // return { format.headings ? { this.map(format.headings, function (h) ) } : "" } { format.columns ? { this.map(format.columns, function (c) ) } : "" } { this.map(iter, function (item) { item.columns ? template.map(item.columns, function (c) ) : "" } ) }
    {h}
    { let (name = item.name || item.names[0], frame = item.definedAt) !frame ? name : template.helpLink(help(item), name, "Title") + {_("io.definedAt")} {sourceLink(frame)} } {c}{desc(item)}
    ; //
    } }); endModule(); // vim: set fdm=marker sw=4 ts=4 et ft=javascript: