1 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
8 Components.utils.import("resource://dactyl/bootstrap.jsm");
9 defineModule("template", {
10 exports: ["Binding", "Template", "template"],
14 default xml namespace = XHTML;
16 var Binding = Class("Binding", {
17 init: function (node, nodes) {
20 node.dactylBinding = this;
22 Object.defineProperties(node, this.constructor.properties);
24 for (let [event, handler] in values(this.constructor.events))
25 node.addEventListener(event, util.wrapCallback(handler, true), false);
28 set collapsed(collapsed) {
30 this.setAttribute("collapsed", "true");
32 this.removeAttribute("collapsed");
34 get collapsed() !!this.getAttribute("collapsed"),
36 __noSuchMethod__: Class.Property({
39 value: function __noSuchMethod__(meth, args) {
40 return this.node[meth].apply(this.node, args);
45 let bindingProto = Object.getPrototypeOf(Binding.prototype);
46 for (let obj = this.prototype; obj !== bindingProto; obj = Object.getPrototypeOf(obj))
50 bind: function bind(func) function bound() {
52 return func.apply(this.dactylBinding, arguments);
60 events: Class.Memoize(function () {
62 for (let obj in this.bindings)
63 if (Object.getOwnPropertyDescriptor(obj, "events"))
64 for (let [event, handler] in Iterator(obj.events))
65 res.push([event, this.bind(handler)]);
69 properties: Class.Memoize(function () {
71 for (let obj in this.bindings)
72 for (let prop in properties(obj)) {
73 let desc = Object.getOwnPropertyDescriptor(obj, prop);
74 if (desc.enumerable) {
75 for (let k in values(["get", "set", "value"]))
76 if (typeof desc[k] === "function")
77 desc[k] = this.bind(desc[k]);
85 ["appendChild", "getAttribute", "insertBefore", "setAttribute"].forEach(function (key) {
86 Object.defineProperty(Binding.prototype, key, {
89 value: function () this.node[key].apply(this.node, arguments),
94 var Template = Module("Template", {
95 add: function add(a, b) a + b,
96 join: function join(c) function (a, b) a + c + b,
98 map: function map(iter, func, sep, interruptable) {
99 XML.ignoreWhitespace = XML.prettyPrinting = false;
100 if (typeof iter.length == "number") // FIXME: Kludge?
101 iter = array.iterValues(iter);
104 for each (let i in Iterator(iter)) {
105 let val = func(i, n);
106 if (val == undefined)
110 if (interruptable && n % interruptable == 0)
111 util.threadYield(true, true);
118 Button: Class("Button", Binding, {
119 init: function init(node, params) {
120 init.supercall(this, node);
122 this.target = params.commandTarget;
125 get command() this.getAttribute("command") || this.getAttribute("key"),
128 "click": function onClick(event) {
129 event.preventDefault();
130 if (this.commandAllowed) {
131 if (Set.has(this.target.commands || {}, this.command))
132 this.target.commands[this.command].call(this.target);
134 this.target.command(this.command);
139 get commandAllowed() {
140 if (Set.has(this.target.allowedCommands || {}, this.command))
141 return this.target.allowedCommands[this.command];
142 if ("commandAllowed" in this.target)
143 return this.target.commandAllowed(this.command);
147 update: function update() {
148 let collapsed = this.collapsed;
149 this.collapsed = !this.commandAllowed;
151 if (collapsed == this.commandAllowed) {
152 let event = this.node.ownerDocument.createEvent("Events");
153 event.initEvent("dactyl-commandupdate", true, false);
154 this.node.ownerDocument.dispatchEvent(event);
159 Events: Class("Events", Binding, {
160 init: function init(node, params) {
161 init.supercall(this, node);
163 let obj = params.eventTarget;
164 let events = obj[this.getAttribute("events") || "events"];
165 if (Set.has(events, "input"))
166 events["dactyl-input"] = events["input"];
168 for (let [event, handler] in Iterator(events))
169 node.addEventListener(event, util.wrapCallback(obj.closure(handler), true), false);
174 bookmarkDescription: function (item, text)
177 !(item.extra && item.extra.length) ? "" :
178 <span highlight="URLExtra">
180 template.map(item.extra, function (e)
181 <>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
185 <a xmlns:dactyl={NS} identifier={item.id == null ? "" : item.id} dactyl:command={item.command || ""}
186 href={item.item.url} highlight="URL">{text || ""}</a>
189 filter: function (str) <span highlight="Filter">{str}</span>,
191 completionRow: function completionRow(item, highlightGroup) {
192 if (typeof icon == "function")
195 if (highlightGroup) {
196 var text = item[0] || "";
197 var desc = item[1] || "";
200 var text = this.processor[0].call(this, item, item.result);
201 var desc = this.processor[1].call(this, item, item.description);
204 XML.ignoreWhitespace = XML.prettyPrinting = false;
206 return <div highlight={highlightGroup || "CompItem"} style="white-space: nowrap">
207 <!-- The non-breaking spaces prevent empty elements
208 - from pushing the baseline down and enlarging
211 <li highlight={"CompResult " + item.highlight}>{text} </li>
212 <li highlight="CompDesc">{desc} </li>
217 helpLink: function (token, text, type) {
218 if (!help.initialized)
219 util.dactyl.initHelp();
221 let topic = token; // FIXME: Evil duplication!
222 if (/^\[.*\]$/.test(topic))
223 topic = topic.slice(1, -1);
224 else if (/^n_/.test(topic))
225 topic = topic.slice(2);
227 if (help.initialized && !Set.has(help.tags, topic))
228 return <span highlight={type || ""}>{text || token}</span>;
230 XML.ignoreWhitespace = XML.prettyPrinting = false;
231 type = type || (/^'.*'$/.test(token) ? "HelpOpt" :
232 /^\[.*\]$|^E\d{3}$/.test(token) ? "HelpTopic" :
233 /^:\w/.test(token) ? "HelpEx" : "HelpKey");
235 return <a highlight={"InlineHelpLink " + type} tag={topic} href={"dactyl://help-tag/" + topic} dactyl:command="dactyl.help" xmlns:dactyl={NS}>{text || topic}</a>;
237 HelpLink: function (token) {
238 if (!help.initialized)
239 util.dactyl.initHelp();
241 let topic = token; // FIXME: Evil duplication!
242 if (/^\[.*\]$/.test(topic))
243 topic = topic.slice(1, -1);
244 else if (/^n_/.test(topic))
245 topic = topic.slice(2);
247 if (help.initialized && !Set.has(help.tags, topic))
250 XML.ignoreWhitespace = XML.prettyPrinting = false;
251 let tag = (/^'.*'$/.test(token) ? "o" :
252 /^\[.*\]$|^E\d{3}$/.test(token) ? "t" :
253 /^:\w/.test(token) ? "ex" : "k");
255 topic = topic.replace(/^'(.*)'$/, "$1");
256 return <{tag} xmlns={NS}>{topic}</{tag}>;
258 linkifyHelp: function linkifyHelp(str, help) {
259 let re = util.regexp(<![CDATA[
261 (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:[\w[\]]+|.) | \[[\w-;]+\] | E\d{3} )
264 return this.highlightSubstrings(str, (function () {
265 for (let res in re.iterate(str))
266 yield [res.index + res.pre.length, res.tag.length];
267 })(), template[help ? "HelpLink" : "helpLink"]);
270 // Fixes some strange stack rewinds on NS_ERROR_OUT_OF_MEMORY
271 // exceptions that we can't catch.
272 stringify: function stringify(arg) {
277 this._sandbox.arg = arg;
278 return Cu.evalInSandbox("String(arg)", this._sandbox);
281 this._sandbox.arg = null;
285 _sandbox: Class.Memoize(function () Cu.Sandbox(global, { wantXrays: false })),
287 // if "processStrings" is true, any passed strings will be surrounded by " and
288 // any line breaks are displayed as \n
289 highlight: function highlight(arg, processStrings, clip, bw) {
290 XML.ignoreWhitespace = XML.prettyPrinting = false;
291 // some objects like window.JSON or getBrowsers()._browsers need the try/catch
293 let str = this.stringify(arg);
295 str = util.clip(str, clip);
296 switch (arg == null ? "undefined" : typeof arg) {
298 return <span highlight="Number">{str}</span>;
302 return <span highlight="String">{str}</span>;
304 return <span highlight="Boolean">{str}</span>;
306 if (arg instanceof Ci.nsIDOMElement) // wtf?
307 return util.objectToString(arg, !bw);
309 str = str.replace("/* use strict */ \n", "/* use strict */ ");
311 return <span highlight="Function">{str.replace(/\{(.|\n)*(?:)/g, "{ ... }")}</span>;
313 arg = String(arg).replace("/* use strict */ \n", "/* use strict */ ");
316 return <span highlight="Null">{arg}</span>;
318 if (arg instanceof Ci.nsIDOMElement)
319 return util.objectToString(arg, !bw);
321 // for java packages value.toString() would crash so badly
322 // that we cannot even try/catch it
323 if (/^\[JavaPackage.*\]$/.test(arg))
324 return <>[JavaPackage]</>;
325 if (processStrings && false)
326 str = template.highlightFilter(str, "\n", function () <span highlight="NonText">^J</span>);
327 return <span highlight="Object">{str}</span>;
331 return <![CDATA[<unknown type>]]>;
335 return <![CDATA[<unknown>]]>;
339 highlightFilter: function highlightFilter(str, filter, highlight, isURI) {
341 str = util.losslessDecodeURI(str);
343 return this.highlightSubstrings(str, (function () {
344 if (filter.length == 0)
347 XML.ignoreWhitespace = XML.prettyPrinting = false;
348 let lcstr = String.toLowerCase(str);
349 let lcfilter = filter.toLowerCase();
351 while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
352 yield [start, filter.length];
353 start += filter.length;
355 })(), highlight || template.filter);
358 highlightRegexp: function highlightRegexp(str, re, highlight) {
359 return this.highlightSubstrings(str, (function () {
360 for (let res in util.regexp.iterate(re, str))
361 yield [res.index, res[0].length, res.wholeMatch ? [res] : res];
362 })(), highlight || template.filter);
365 highlightSubstrings: function highlightSubstrings(str, iter, highlight) {
366 XML.ignoreWhitespace = XML.prettyPrinting = false;
367 if (typeof str == "xml")
372 str = String(str).replace(" ", "\u00a0");
376 for (let [i, length, args] in iter) {
377 if (i == _i || i < _i)
381 XML.ignoreWhitespace = false;
382 s += <>{str.substring(start, i)}</>;
383 s += highlight.apply(this, Array.concat(args || str.substr(i, length)));
386 return s + <>{str.substr(start)}</>;
389 highlightURL: function highlightURL(str, force) {
390 if (force || /^[a-zA-Z]+:\/\//.test(str))
391 return <a highlight="URL" href={str}>{util.losslessDecodeURI(str)}</a>;
396 icon: function (item, text) <>
397 <span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}
400 jumps: function jumps(index, elems) {
401 XML.ignoreWhitespace = XML.prettyPrinting = false;
404 <tr style="text-align: left;" highlight="Title">
405 <th colspan="2">{_("title.Jump")}</th>
406 <th>{_("title.HPos")}</th>
407 <th>{_("title.VPos")}</th>
408 <th>{_("title.Title")}</th>
409 <th>{_("title.URI")}</th>
412 this.map(Iterator(elems), function ([idx, val])
414 <td class="indicator">{idx == index ? ">" : ""}</td>
415 <td>{Math.abs(idx - index)}</td>
416 <td>{val.offset ? val.offset.x : ""}</td>
417 <td>{val.offset ? val.offset.y : ""}</td>
418 <td style="width: 250px; max-width: 500px; overflow: hidden;">{val.title}</td>
419 <td><a href={val.URI.spec} highlight="URL jump-list">{util.losslessDecodeURI(val.URI.spec)}</a></td>
426 options: function options(title, opts, verbose) {
427 XML.ignoreWhitespace = XML.prettyPrinting = false;
430 <tr highlight="Title" align="left">
431 <th>--- {title} ---</th>
434 this.map(opts, function (opt)
437 <div highlight="Message"
438 ><span style={opt.isDefault ? "" : "font-weight: bold"}>{opt.pre}{opt.name}</span><span>{opt.value}</span>{
439 opt.isDefault || opt.default == null ? "" : <span class="extra-info"> (default: {opt.default})</span>
441 verbose && opt.setFrom ? <div highlight="Message"> Last set from {template.sourceLink(opt.setFrom)}</div> : <></>
450 sourceLink: function (frame) {
451 let url = util.fixURI(frame.filename || "unknown");
452 let path = util.urlPath(url);
454 XML.ignoreWhitespace = XML.prettyPrinting = false;
455 return <a xmlns:dactyl={NS} dactyl:command="buffer.viewSource"
456 href={url} path={path} line={frame.lineNumber}
458 path + ":" + frame.lineNumber
462 table: function table(title, data, indent) {
463 XML.ignoreWhitespace = XML.prettyPrinting = false;
466 <tr highlight="Title" align="left">
467 <th colspan="2">{title}</th>
470 this.map(data, function (datum)
472 <td style={"font-weight: bold; min-width: 150px; padding-left: " + (indent || "2ex")}>{datum[0]}</td>
478 if (table.tr.length() > 1)
482 tabular: function tabular(headings, style, iter) {
483 // TODO: This might be mind-bogglingly slow. We'll see.
484 XML.ignoreWhitespace = XML.prettyPrinting = false;
487 <tr highlight="Title" align="left">
489 this.map(headings, function (h)
494 this.map(iter, function (row)
497 template.map(Iterator(row), function ([i, d])
498 <td style={style[i] || ""}>{d}</td>)
506 usage: function usage(iter, format) {
507 XML.ignoreWhitespace = XML.prettyPrinting = false;
508 format = format || {};
509 let desc = format.description || function (item) template.linkifyHelp(item.description);
510 let help = format.help || function (item) item.name;
511 function sourceLink(frame) {
512 let source = template.sourceLink(frame);
513 source.@NS::hint = source.text();
519 <thead highlight="UsageHead">
520 <tr highlight="Title" align="left">
522 this.map(format.headings, function (h) <th>{h}</th>)
530 this.map(format.columns, function (c) <col style={c}/>)
534 <tbody highlight="UsageBody">{
535 this.map(iter, function (item)
536 <tr highlight="UsageItem">
537 <td style="padding-right: 2em;">
538 <span highlight="Usage Link">{
539 let (name = item.name || item.names[0], frame = item.definedAt)
541 template.helpLink(help(item), name, "Title") +
542 <span highlight="LinkInfo" xmlns:dactyl={NS}>{_("io.definedAt")} {sourceLink(frame)}</span>
545 { item.columns ? template.map(item.columns, function (c) <td>{c}</td>) : "" }
546 <td>{desc(item)}</td>
556 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: