1 // Copyright (c) 2008-2012 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 defineModule("template", {
9 exports: ["Binding", "Template", "template"],
13 lazyRequire("help", ["help"]);
15 var Binding = Class("Binding", {
16 init: function (node, nodes) {
19 node.dactylBinding = this;
21 Object.defineProperties(node, this.constructor.properties);
23 for (let [event, handler] in values(this.constructor.events))
24 node.addEventListener(event, util.wrapCallback(handler, true), false);
27 set collapsed(collapsed) {
29 this.setAttribute("collapsed", "true");
31 this.removeAttribute("collapsed");
33 get collapsed() !!this.getAttribute("collapsed"),
35 __noSuchMethod__: Class.Property({
38 value: function __noSuchMethod__(meth, args) {
39 return this.node[meth].apply(this.node, args);
44 let bindingProto = Object.getPrototypeOf(Binding.prototype);
45 for (let obj = this.prototype; obj !== bindingProto; obj = Object.getPrototypeOf(obj))
49 bind: function bind(func) function bound() {
51 return func.apply(this.dactylBinding, arguments);
59 events: Class.Memoize(function () {
61 for (let obj in this.bindings)
62 if (Object.getOwnPropertyDescriptor(obj, "events"))
63 for (let [event, handler] in Iterator(obj.events))
64 res.push([event, this.bind(handler)]);
68 properties: Class.Memoize(function () {
70 for (let obj in this.bindings)
71 for (let prop in properties(obj)) {
72 let desc = Object.getOwnPropertyDescriptor(obj, prop);
73 if (desc.enumerable) {
74 for (let k in values(["get", "set", "value"]))
75 if (typeof desc[k] === "function")
76 desc[k] = this.bind(desc[k]);
84 ["appendChild", "getAttribute", "insertBefore", "setAttribute"].forEach(function (key) {
85 Object.defineProperty(Binding.prototype, key, {
88 value: function () this.node[key].apply(this.node, arguments),
93 var Template = Module("Template", {
96 Button: Class("Button", Binding, {
97 init: function init(node, params) {
98 init.supercall(this, node);
100 this.target = params.commandTarget;
103 get command() this.getAttribute("command") || this.getAttribute("key"),
106 "click": function onClick(event) {
107 event.preventDefault();
108 if (this.commandAllowed) {
109 if (Set.has(this.target.commands || {}, this.command))
110 this.target.commands[this.command].call(this.target);
112 this.target.command(this.command);
117 get commandAllowed() {
118 if (Set.has(this.target.allowedCommands || {}, this.command))
119 return this.target.allowedCommands[this.command];
120 if ("commandAllowed" in this.target)
121 return this.target.commandAllowed(this.command);
125 update: function update() {
126 let collapsed = this.collapsed;
127 this.collapsed = !this.commandAllowed;
129 if (collapsed == this.commandAllowed) {
130 let event = this.node.ownerDocument.createEvent("Events");
131 event.initEvent("dactyl-commandupdate", true, false);
132 this.node.ownerDocument.dispatchEvent(event);
137 Events: Class("Events", Binding, {
138 init: function init(node, params) {
139 init.supercall(this, node);
141 let obj = params.eventTarget;
142 let events = obj[this.getAttribute("events") || "events"];
143 if (Set.has(events, "input"))
144 events["dactyl-input"] = events["input"];
146 for (let [event, handler] in Iterator(events))
147 node.addEventListener(event, util.wrapCallback(obj.closure(handler), true), false);
152 map: function map(iter, func, sep, interruptable) {
153 if (typeof iter.length == "number") // FIXME: Kludge?
154 iter = array.iterValues(iter);
158 for each (let i in Iterator(iter)) {
159 let val = func(i, n);
160 if (val == undefined)
164 if (interruptable && n % interruptable == 0)
165 util.threadYield(true, true);
172 bookmarkDescription: function (item, text) [
173 !(item.extra && item.extra.length) ? [] :
174 ["span", { highlight: "URLExtra" },
176 template.map(item.extra, function (e)
178 ["span", { highlight: e[2] }, e[1]]],
181 ["a", { identifier: item.id == null ? "" : item.id,
182 "dactyl:command": item.command || "",
183 href: item.item.url, highlight: "URL" },
187 filter: function (str) ["span", { highlight: "Filter" }, str],
189 completionRow: function completionRow(item, highlightGroup) {
190 if (typeof icon == "function")
193 if (highlightGroup) {
194 var text = item[0] || "";
195 var desc = item[1] || "";
198 var text = this.processor[0].call(this, item, item.result);
199 var desc = this.processor[1].call(this, item, item.description);
202 return ["div", { highlight: highlightGroup || "CompItem", style: "white-space: nowrap" },
203 /* The non-breaking spaces prevent empty elements
204 * from pushing the baseline down and enlarging
207 ["li", { highlight: "CompResult " + item.highlight },
209 ["li", { highlight: "CompDesc" },
213 helpLink: function (token, text, type) {
214 if (!help.initialized)
215 util.dactyl.initHelp();
217 let topic = token; // FIXME: Evil duplication!
218 if (/^\[.*\]$/.test(topic))
219 topic = topic.slice(1, -1);
220 else if (/^n_/.test(topic))
221 topic = topic.slice(2);
223 if (help.initialized && !Set.has(help.tags, topic))
224 return ["span", { highlight: type || ""}, text || token];
226 type = type || (/^'.*'$/.test(token) ? "HelpOpt" :
227 /^\[.*\]$|^E\d{3}$/.test(token) ? "HelpTopic" :
228 /^:\w/.test(token) ? "HelpEx" : "HelpKey");
230 return ["a", { highlight: "InlineHelpLink " + type, tag: topic,
231 href: "dactyl://help-tag/" + topic,
232 "dactyl:command": "dactyl.help" },
235 HelpLink: function (token) {
236 if (!help.initialized)
237 util.dactyl.initHelp();
239 let topic = token; // FIXME: Evil duplication!
240 if (/^\[.*\]$/.test(topic))
241 topic = topic.slice(1, -1);
242 else if (/^n_/.test(topic))
243 topic = topic.slice(2);
245 if (help.initialized && !Set.has(help.tags, topic))
248 let tag = (/^'.*'$/.test(token) ? "o" :
249 /^\[.*\]$|^E\d{3}$/.test(token) ? "t" :
250 /^:\w/.test(token) ? "ex" : "k");
252 topic = topic.replace(/^'(.*)'$/, "$1");
253 return [tag, { xmlns: "dactyl" }, topic];
255 linkifyHelp: function linkifyHelp(str, help) {
256 let re = util.regexp(literal(/*
258 (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:[\w[\]]+|.) | \[[\w-;]+\] | E\d{3} )
261 return this.highlightSubstrings(str, (function () {
262 for (let res in re.iterate(str))
263 yield [res.index + res.pre.length, res.tag.length];
264 })(), this[help ? "HelpLink" : "helpLink"]);
268 // Fixes some strange stack rewinds on NS_ERROR_OUT_OF_MEMORY
269 // exceptions that we can't catch.
270 stringify: function stringify(arg) {
275 this._sandbox.arg = arg;
276 return Cu.evalInSandbox("String(arg)", this._sandbox);
279 this._sandbox.arg = null;
283 _sandbox: Class.Memoize(function () Cu.Sandbox(Cu.getGlobalForObject(global),
284 { wantXrays: false })),
286 // if "processStrings" is true, any passed strings will be surrounded by " and
287 // any line breaks are displayed as \n
288 highlight: function highlight(arg, processStrings, clip, bw) {
289 // some objects like window.JSON or getBrowsers()._browsers need the try/catch
291 let str = this.stringify(arg);
293 str = util.clip(str, clip);
294 switch (arg == null ? "undefined" : typeof arg) {
296 return ["span", { highlight: "Number" }, str];
300 return ["span", { highlight: "String" }, str];
302 return ["span", { highlight: "Boolean" }, str];
304 if (arg instanceof Ci.nsIDOMElement) // wtf?
305 return util.objectToString(arg, !bw);
307 str = str.replace("/* use strict */ \n", "/* use strict */ ");
309 return ["span", { highlight: "Function" },
310 str.replace(/\{(.|\n)*(?:)/g, "{ ... }")];
311 arg = String(arg).replace("/* use strict */ \n", "/* use strict */ ");
314 return ["span", { highlight: "Null" }, "undefined"];
316 if (arg instanceof Ci.nsIDOMElement)
317 return util.objectToString(arg, !bw);
318 if (arg instanceof util.Magic)
321 if (processStrings && false)
322 str = template._highlightFilter(str, "\n",
323 function () ["span", { highlight: "NonText" },
325 return ["span", { highlight: "Object" }, str];
329 return "<unknown type>";
333 return "<error: " + e + ">";
337 highlightFilter: function highlightFilter(str, filter, highlight, isURI) {
339 str = util.losslessDecodeURI(str);
341 return this.highlightSubstrings(str, (function () {
342 if (filter.length == 0)
345 let lcstr = String.toLowerCase(str);
346 let lcfilter = filter.toLowerCase();
348 while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
349 yield [start, filter.length];
350 start += filter.length;
352 })(), highlight || template.filter);
355 highlightRegexp: function highlightRegexp(str, re, highlight) {
356 return this.highlightSubstrings(str, (function () {
357 for (let res in util.regexp.iterate(re, str))
358 yield [res.index, res[0].length, res.wholeMatch ? [res] : res];
359 })(), highlight || template.filter);
362 highlightSubstrings: function highlightSubstrings(str, iter, highlight) {
367 return DOM.DOMString(str);
372 for (let [i, length, args] in iter) {
373 if (i == _i || i < _i)
377 s.push(str.substring(start, i),
378 highlight.apply(this, Array.concat(args || str.substr(i, length))));
381 s.push(str.substr(start));
385 highlightURL: function highlightURL(str, force) {
386 if (force || /^[a-zA-Z]+:\/\//.test(str))
387 return ["a", { highlight: "URL", href: str },
388 util.losslessDecodeURI(str)];
393 icon: function (item, text) [
394 ["span", { highlight: "CompIcon" },
395 item.icon ? ["img", { src: item.icon }] : []],
396 ["span", { class: "td-strut" }],
400 jumps: function jumps(index, elems) {
402 ["tr", { style: "text-align: left;", highlight: "Title" },
403 ["th", { colspan: "2" }, _("title.Jump")],
404 ["th", {}, _("title.HPos")],
405 ["th", {}, _("title.VPos")],
406 ["th", {}, _("title.Title")],
407 ["th", {}, _("title.URI")]],
408 this.map(Iterator(elems), function ([idx, val])
410 ["td", { class: "indicator" }, idx == index ? ">" : ""],
411 ["td", {}, Math.abs(idx - index)],
412 ["td", {}, val.offset ? val.offset.x : ""],
413 ["td", {}, val.offset ? val.offset.y : ""],
414 ["td", { style: "width: 250px; max-width: 500px; overflow: hidden;" }, val.title],
416 ["a", { href: val.URI.spec, highlight: "URL jump-list" },
417 util.losslessDecodeURI(val.URI.spec)]]])];
421 options: function options(title, opts, verbose) {
423 ["tr", { highlight: "Title", align: "left" },
424 ["th", {}, "--- " + title + " ---"]],
425 this.map(opts, function (opt)
428 ["div", { highlight: "Message" },
429 ["span", { style: opt.isDefault ? "" : "font-weight: bold" },
431 ["span", {}, opt.value],
432 opt.isDefault || opt.default == null ? "" : ["span", { class: "extra-info" }, " (default: ", opt.default, ")"]],
433 verbose && opt.setFrom ? ["div", { highlight: "Message" },
435 template.sourceLink(opt.setFrom)] : ""]])];
438 sourceLink: function (frame) {
439 let url = util.fixURI(frame.filename || "unknown");
440 let path = util.urlPath(url);
442 return ["a", { "dactyl:command": "buffer.viewSource",
443 href: url, path: path, line: frame.lineNumber,
445 path + ":" + frame.lineNumber];
448 table: function table(title, data, indent) {
449 let table = ["table", {},
450 ["tr", { highlight: "Title", align: "left" },
451 ["th", { colspan: "2" }, title]],
452 this.map(data, function (datum)
454 ["td", { style: "font-weight: bold; min-width: 150px; padding-left: " + (indent || "2ex") }, datum[0]],
455 ["td", {}, datum[1]]])];
461 tabular: function tabular(headings, style, iter) {
463 // TODO: This might be mind-bogglingly slow. We'll see.
465 ["tr", { highlight: "Title", align: "left" },
466 this.map(headings, function (h)
468 this.map(iter, function (row)
470 self.map(Iterator(row), function ([i, d])
471 ["td", { style: style[i] || "" }, d])])];
474 usage: function usage(iter, format) {
477 format = format || {};
478 let desc = format.description || function (item) self.linkifyHelp(item.description);
479 let help = format.help || function (item) item.name;
480 function sourceLink(frame) {
481 let source = self.sourceLink(frame);
482 source[1]["dactyl:hint"] = source[2];
487 ["thead", { highlight: "UsageHead" },
488 ["tr", { highlight: "Title", align: "left" },
489 this.map(format.headings, function (h) ["th", {}, h])]] :
493 this.map(format.columns, function (c) ["col", { style: c }])] :
495 ["tbody", { highlight: "UsageBody" },
496 this.map(iter, function (item)
498 let (name = item.name || item.names[0], frame = item.definedAt)
499 ["tr", { highlight: "UsageItem" },
500 ["td", { style: "padding-right: 2em;" },
501 ["span", { highlight: "Usage Link" },
503 [self.helpLink(help(item), name, "Title"),
504 ["span", { highlight: "LinkInfo" },
505 _("io.definedAt"), " ",
506 sourceLink(frame)]]]],
507 item.columns ? self.map(item.columns, function (c) ["td", {}, c]) : [],
508 ["td", {}, desc(item)]])]]
514 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: