1 // Copyright (c) 2008-2014 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 (hasOwnProperty(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 (hasOwnProperty(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 (hasOwnProperty(events, "input"))
144 events["dactyl-input"] = events["input"];
146 for (let [event, handler] in Iterator(events))
147 node.addEventListener(event, util.wrapCallback(handler.bind(obj), 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 (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);
171 bookmarkDescription: function (item, text) [
172 !(item.extra && item.extra.length) ? [] :
173 ["span", { highlight: "URLExtra" },
175 template.map(item.extra, e =>
177 ["span", { highlight: e[2] }, e[1]]],
180 ["a", { identifier: item.id == null ? "" : item.id,
181 "dactyl:command": item.command || "",
182 href: item.item.url, highlight: "URL" },
186 filter: function (str) ["span", { highlight: "Filter" }, str],
188 completionRow: function completionRow(item, highlightGroup) {
189 if (typeof icon == "function")
192 if (highlightGroup) {
193 var text = item[0] || "";
194 var desc = item[1] || "";
197 var text = this.processor[0].call(this, item, item.result);
198 var desc = this.processor[1].call(this, item, item.description);
201 return ["div", { highlight: highlightGroup || "CompItem", style: "white-space: nowrap" },
202 /* The non-breaking spaces prevent empty elements
203 * from pushing the baseline down and enlarging
206 ["li", { highlight: "CompResult " + item.highlight },
208 ["li", { highlight: "CompDesc" },
212 helpLink: function (token, text, type) {
213 if (!help.initialized)
214 util.dactyl.initHelp();
216 let topic = token; // FIXME: Evil duplication!
217 if (/^\[.*\]$/.test(topic))
218 topic = topic.slice(1, -1);
219 else if (/^n_/.test(topic))
220 topic = topic.slice(2);
222 if (help.initialized && !hasOwnProperty(help.tags, topic))
223 return ["span", { highlight: type || ""}, text || token];
225 type = type || (/^'.*'$/.test(token) ? "HelpOpt" :
226 /^\[.*\]$|^E\d{3}$/.test(token) ? "HelpTopic" :
227 /^:\w/.test(token) ? "HelpEx" : "HelpKey");
229 return ["a", { highlight: "InlineHelpLink " + type, tag: topic,
230 href: "dactyl://help-tag/" + topic,
231 "dactyl:command": "dactyl.help" },
234 HelpLink: function (token) {
235 if (!help.initialized)
236 util.dactyl.initHelp();
238 let topic = token; // FIXME: Evil duplication!
239 if (/^\[.*\]$/.test(topic))
240 topic = topic.slice(1, -1);
241 else if (/^n_/.test(topic))
242 topic = topic.slice(2);
244 if (help.initialized && !hasOwnProperty(help.tags, topic))
247 let tag = (/^'.*'$/.test(token) ? "o" :
248 /^\[.*\]$|^E\d{3}$/.test(token) ? "t" :
249 /^:\w/.test(token) ? "ex" : "k");
251 topic = topic.replace(/^'(.*)'$/, "$1");
252 return [tag, { xmlns: "dactyl" }, topic];
254 linkifyHelp: function linkifyHelp(str, help) {
255 let re = util.regexp(literal(/*
257 (?P<tag> '[\w-]+' | :(?:[\w-]+!?|!) | (?:._)?<[\w-]+>\w* | \b[a-zA-Z]_(?:[\w[\]]+|.) | \[[\w-;]+\] | E\d{3} )
260 return this.highlightSubstrings(str, (function () {
261 for (let res in re.iterate(str))
262 yield [res.index + res.pre.length, res.tag.length];
263 })(), this[help ? "HelpLink" : "helpLink"]);
266 // if "processStrings" is true, any passed strings will be surrounded by " and
267 // any line breaks are displayed as \n
268 highlight: function highlight(arg, processStrings, clip, bw) {
269 // some objects like window.JSON or getBrowsers()._browsers need the try/catch
271 let str = String(arg);
273 str = util.clip(str, clip);
274 switch (arg == null ? "undefined" : typeof arg) {
276 return ["span", { highlight: "Number" }, str];
280 return ["span", { highlight: "String" }, str];
282 return ["span", { highlight: "Boolean" }, str];
284 if (arg instanceof Ci.nsIDOMElement) // wtf?
285 return util.objectToString(arg, !bw);
287 str = str.replace("/* use strict */ \n", "/* use strict */ ");
289 return ["span", { highlight: "Function" },
290 str.replace(/\{(.|\n)*(?:)/g, "{ ... }")];
291 arg = String(arg).replace("/* use strict */ \n", "/* use strict */ ");
294 return ["span", { highlight: "Null" }, "undefined"];
296 if (arg instanceof Ci.nsIDOMElement)
297 return util.objectToString(arg, !bw);
298 if (arg instanceof util.Magic)
301 if (processStrings && false)
302 str = template._highlightFilter(str, "\n",
303 function () ["span", { highlight: "NonText" },
305 return ["span", { highlight: "Object" }, str];
309 return "<unknown type>";
313 return "<error: " + e + ">";
317 highlightFilter: function highlightFilter(str, filter, highlight, isURI) {
319 str = util.losslessDecodeURI(str);
321 return this.highlightSubstrings(str, (function () {
322 if (filter.length == 0)
325 let lcstr = String.toLowerCase(str);
326 let lcfilter = filter.toLowerCase();
328 while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
329 yield [start, filter.length];
330 start += filter.length;
332 })(), highlight || template.filter);
335 highlightRegexp: function highlightRegexp(str, re, highlight) {
336 return this.highlightSubstrings(str, (function () {
337 for (let res in util.regexp.iterate(re, str))
338 yield [res.index, res[0].length, res.wholeMatch ? [res] : res];
339 })(), highlight || template.filter);
342 highlightSubstrings: function highlightSubstrings(str, iter, highlight) {
347 return DOM.DOMString(str);
352 for (let [i, length, args] in iter) {
353 if (i == _i || i < _i)
357 s.push(str.substring(start, i),
358 highlight.apply(this, Array.concat(args || str.substr(i, length))));
361 s.push(str.substr(start));
365 highlightURL: function highlightURL(str, force) {
366 if (force || /^[a-zA-Z]+:\/\//.test(str))
367 return ["a", { highlight: "URL", href: str },
368 util.losslessDecodeURI(str)];
373 icon: function (item, text) [
374 ["span", { highlight: "CompIcon" },
375 item.icon ? ["img", { src: item.icon }] : []],
376 ["span", { class: "td-strut" }],
380 jumps: function jumps(index, elems) {
382 ["tr", { style: "text-align: left;", highlight: "Title" },
383 ["th", { colspan: "2" }, _("title.Jump")],
384 ["th", {}, _("title.HPos")],
385 ["th", {}, _("title.VPos")],
386 ["th", {}, _("title.Title")],
387 ["th", {}, _("title.URI")]],
388 this.map(Iterator(elems), ([idx, val]) =>
390 ["td", { class: "indicator" }, idx == index ? ">" : ""],
391 ["td", {}, Math.abs(idx - index)],
392 ["td", {}, val.offset ? val.offset.x : ""],
393 ["td", {}, val.offset ? val.offset.y : ""],
394 ["td", { style: "width: 250px; max-width: 500px; overflow: hidden;" }, val.title],
396 ["a", { href: val.URI.spec, highlight: "URL jump-list" },
397 util.losslessDecodeURI(val.URI.spec)]]])];
400 options: function options(title, opts, verbose) {
402 ["tr", { highlight: "Title", align: "left" },
403 ["th", {}, "--- " + title + " ---"]],
404 this.map(opts, opt =>
407 ["div", { highlight: "Message" },
408 ["span", { style: opt.isDefault ? "" : "font-weight: bold" },
410 ["span", {}, opt.value],
411 opt.isDefault || opt.default == null ? "" : ["span", { class: "extra-info" }, " (default: ", opt.default, ")"]],
412 verbose && opt.setFrom ? ["div", { highlight: "Message" },
414 template.sourceLink(opt.setFrom)] : ""]])];
417 sourceLink: function (frame) {
418 let url = util.fixURI(frame.filename || "unknown");
419 let path = util.urlPath(url);
421 return ["a", { "dactyl:command": "buffer.viewSource",
422 href: url, path: path, line: frame.lineNumber,
424 path + ":" + frame.lineNumber];
427 table: function table(title, data, indent) {
428 let table = ["table", {},
429 ["tr", { highlight: "Title", align: "left" },
430 ["th", { colspan: "2" }, title]],
431 this.map(data, datum =>
433 ["td", { style: "font-weight: bold; min-width: 150px; padding-left: " + (indent || "2ex") }, datum[0]],
434 ["td", {}, datum[1]]])];
440 tabular: function tabular(headings, style, iter) {
441 // TODO: This might be mind-bogglingly slow. We'll see.
443 ["tr", { highlight: "Title", align: "left" },
444 this.map(headings, function (h)
446 this.map(iter, (row) =>
448 this.map(Iterator(row), ([i, d]) =>
449 ["td", { style: style[i] || "" }, d])])];
452 usage: function usage(iter, format={}) {
453 let desc = format.description || (item => this.linkifyHelp(item.description));
454 let help = format.help || (item => item.name);
455 let sourceLink = (frame) => {
456 let source = this.sourceLink(frame);
457 source[1]["dactyl:hint"] = source[2];
462 ["thead", { highlight: "UsageHead" },
463 ["tr", { highlight: "Title", align: "left" },
464 this.map(format.headings, (h) => ["th", {}, h])]] :
468 this.map(format.columns, (c) => ["col", { style: c }])] :
470 ["tbody", { highlight: "UsageBody" },
471 this.map(iter, (item) =>
473 let (name = item.name || item.names[0], frame = item.definedAt)
474 ["tr", { highlight: "UsageItem" },
475 ["td", { style: "padding-right: 2em;" },
476 ["span", { highlight: "Usage Link" },
478 [this.helpLink(help(item), name, "Title"),
479 ["span", { highlight: "LinkInfo" },
480 _("io.definedAt"), " ",
481 sourceLink(frame)]]]],
482 item.columns ? this.map(item.columns, (c) => ["td", {}, c]) : [],
483 ["td", {}, desc(item)]])]];
489 // vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: