view-as-mime-type: fix bug in interactive declaration
[conkeror.git] / modules / help.js
blobc6324adb69ff3b78dc68cbffb0a259848706301c
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2008 Nelson Elhage
4  * (C) Copyright 2008 David Glasser
5  *
6  * Use, modification, and distribution are subject to the terms specified in the
7  * COPYING file.
8 **/
10 require("special-buffer.js");
11 require("interactive.js");
13 function where_is_command (buffer, command) {
14     var list = find_command_in_keymap(buffer, command);
15     var msg;
16     if (list.length == 0)
17         msg = command + " is not on any key";
18     else
19         msg = command + " is on " + list.join(", ");
20     buffer.window.minibuffer.message(msg);
22 interactive("where-is", null, function (I) {
23     where_is_command(I.buffer,
24                      (yield I.minibuffer.read_command($prompt = "Where is command:")));
25 });
27 function help_document_generator (document, buffer) {
28     dom_generator.call(this, document, XHTML_NS);
29     this.buffer = buffer;
31 help_document_generator.prototype = {
32     __proto__: dom_generator.prototype,
34     key_binding : function (str, parent) {
35         var node = this.element("span", "class", "key-binding");
36         this.text(str, node);
37         if (parent)
38             parent.appendChild(node);
39         return node;
40     },
42     source_code_reference : function (ref, parent) {
43         var f = this.document.createDocumentFragment();
44         var module_name = ref.module_name;
45         //f.appendChild(this.text(module_name != null ? "module " : "file "));
46         var x = this.element("a",
47                              "class", "source-code-reference",
48                              "href", "javascript:");
49         x.addEventListener("click", function (event) {
50             co_call(ref.open_in_editor());
51             event.preventDefault();
52             event.stopPropagation();
53         }, false /* capture */, false /* allow untrusted */);
54         x.textContent = (module_name != null ? module_name : ref.file_name);
55         f.appendChild(x);
56         if (parent)
57             parent.appendChild(f);
58         return f;
59     },
61     command_name : function (name, parent) {
62         var node = this.element("span", "class", "command");
63         this.text(name, node);
64         if (parent)
65             parent.appendChild(node);
66         return node;
67     },
69     command_reference : function (name, parent) {
70         var node = this.element("a",
71                                 "class", "command",
72                                 "href", "javascript:");
73         var buffer = this.buffer;
74         node.addEventListener("click", function (event) {
75                                   /* FIXME: don't hardcode browse target */
76                                   describe_command(buffer, name, OPEN_NEW_BUFFER);
77                                   event.preventDefault();
78                                   event.stopPropagation();
79             }, false /* capture */, false /* allow untrusted */);
80         this.text(name, node);
81         if (parent)
82             parent.appendChild(node);
83         return node;
84     },
86     variable_reference : function (name, parent) {
87         var node = this.element("a", "class", "variable", "href", "#");
88         /* FIXME: make this work */
89         this.text(name, node);
90         if (parent)
91             parent.appendChild(node);
92         return node;
93     },
95     help_text : function (str, parent) {
96         var paras = str.split("\n");
97         var f = this.document.createDocumentFragment();
98         for (var i = 0; i < paras.length; ++i) {
99             var para = paras[i];
100             if (para.length == 0)
101                 continue;
103             var p = this.element("p", f);
105             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
107             var match;
108             var last_index = 0;
109             while ((match = regexp.exec(para)) != null) {
110                 this.text(para.substring(last_index, match.index), p);
111                 var command = match[1];
112                 /* FIXME: check if it is a valid command */
113                 this.command_reference(command, p);
114                 last_index = regexp.lastIndex;
115             }
116             if (last_index < para.length)
117                 this.text(para.substring(last_index), p);
118         }
119         if (parent != null)
120             parent.appendChild(f);
121         return f;
122     },
124     add_help_stylesheet : function () {
125         this.add_stylesheet("chrome://conkeror-gui/content/help.css");
126     }
129 define_keywords("$binding_list");
130 function describe_bindings_buffer (window, element) {
131     this.constructor_begin();
132     keywords(arguments);
133     special_buffer.call(this, window, element, forward_keywords(arguments));
134     this.binding_list = arguments.$binding_list;
135     this.constructor_end();
138 describe_bindings_buffer.prototype = {
140     get keymap() {
141         return help_buffer_keymap;
142     },
144     title : "Key bindings",
146     description : "*bindings*",
148     generate : function () {
149         var d = this.document;
150         var list = this.binding_list;
151         delete this.binding_list;
153         var list_by_keymap = {};
154         var keymap_list = [];
155         for each (let x in list) {
156             let name = x.bound_in || "";
157             let km;
158             if (name in list_by_keymap)
159                 km = list_by_keymap[name];
160             else {
161                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
162                 keymap_list.push(km);
163             }
164             let catname = x.category || "";
165             let cat;
166             if (catname in km.list_by_category)
167                 cat = km.list_by_category[catname];
168             else {
169                 cat = km.list_by_category[catname] = [];
170                 cat.name = catname;
171                 if (catname == "")
172                     km.category_list.unshift(cat);
173                 else
174                     km.category_list.push(cat);
175             }
176             cat.push(x);
177         }
179         var g = new help_document_generator(d, this);
180         g.add_help_stylesheet();
182         d.body.setAttribute("class", "help-list");
184         for each (let km in keymap_list) {
185             g.text(km.name, g.element("h1", d.body));
186             for each (let cat in km.category_list) {
187                 if (cat.name != "")
188                     g.text(cat.name, g.element("h2", d.body));
190                 let table = g.element("table", d.body);
191                 for (var i = 0; i < cat.length; ++i) {
192                     let bind = cat[i];
193                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
194                     let seq_td = g.element("td", tr, "class", "key-binding");
195                     g.text(bind.seq, seq_td);
196                     let command_td = g.element("td", tr, "class", "command");
197                     let help_str = null;
198                     if (bind.command != null) {
199                         if (typeof(bind.command) == "function") {
200                             g.text("[function]", command_td);
201                         } else {
202                             g.text(bind.command, command_td);
203                             let cmd = interactive_commands.get(bind.command);
204                             if (cmd != null)
205                                 help_str = cmd.shortdoc;
206                         }
207                     } else if (bind.fallthrough)
208                         g.text("[pass through]", command_td);
209                     let help_td = g.element("td", tr, "class", "help");
210                     g.text(help_str || "", help_td);
211                 }
212             }
213         }
214     },
216     __proto__: special_buffer.prototype
220 function describe_bindings (buffer, target) {
221     var list = [];
222     for_each_key_binding(buffer, function (binding_stack) {
223             var last = binding_stack[binding_stack.length - 1];
224             if (last.command == null && !last.fallthrough)
225                 return;
226             let bound_in = null;
227         outer:
228             for (let i = binding_stack.length - 1; i >= 0; --i) {
229                 bound_in = binding_stack[i].bound_in;
230                 while (bound_in) {
231                     if (bound_in.name && !bound_in.anonymous)
232                         break outer;
233                     bound_in = bound_in.bound_in;
234                 }
235             }
236             var bind = {seq: format_binding_sequence(binding_stack),
237                         fallthrough: last.fallthrough,
238                         command: last.command,
239                         bound_in: bound_in.name,
240                         category: last.category
241                        };
242             list.push(bind);
243         });
244     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
245                                                 $opener = buffer,
246                                                 $binding_list = list),
247                   target);
249 function describe_bindings_new_buffer (I) {
250     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
252 function describe_bindings_new_window (I) {
253     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
255 interactive("describe-bindings", null,
256             alternates(describe_bindings_new_buffer, describe_bindings_new_window));
259 define_keywords("$command_list");
260 function apropos_command_buffer (window, element) {
261     this.constructor_begin();
262     keywords(arguments);
263     special_buffer.call(this, window, element, forward_keywords(arguments));
264     this.command_list = arguments.$command_list;
265     this.constructor_end();
268 apropos_command_buffer.prototype = {
270     get keymap() {
271         return help_buffer_keymap;
272     },
274     title : "Apropos commands",
276     description : "*Apropos*",
278     generate : function () {
279         var d = this.document;
280         var list = this.command_list;
281         delete this.command_list;
283         var g = new help_document_generator(d, this);
284         g.add_help_stylesheet();
286         d.body.setAttribute("class", "help-list");
288         var table = d.createElementNS(XHTML_NS, "table");
289         for (var i = 0; i < list.length; ++i) {
290             var binding = list[i];
291             var tr = d.createElementNS(XHTML_NS, "tr");
292             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
294             var command_td = d.createElementNS(XHTML_NS,"td");
295             g.command_reference(binding.name, command_td);
297             var shortdoc = "";
298             if (binding.cmd.shortdoc != null)
299                 shortdoc = binding.cmd.shortdoc;
300             tr.appendChild(command_td);
302             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
303             shortdoc_td.setAttribute("class", "help");
304             shortdoc_td.textContent = shortdoc;
305             tr.appendChild(shortdoc_td);
307             table.appendChild(tr);
308         }
309         d.body.appendChild(table);
310     },
312     __proto__: special_buffer.prototype
316 /* TODO: support regexps/etc. */
317 function apropos_command (buffer, substring, target) {
318     var list = [];
319     interactive_commands.for_each(function (name, cmd) {
320         if (name.indexOf(substring) != -1) {
321             var binding = {name: name, cmd: cmd};
322             list.push(binding);
323         }
324     });
325     list.sort(function (a,b) {
326                   if (a.name < b.name)
327                       return -1;
328                   if (a.name > b.name)
329                       return 1;
330                   return 0
331               });
332     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
333                                                 $opener = buffer,
334                                                 $command_list = list),
335                   target);
338 function apropos_command_new_buffer (I) {
339     apropos_command(I.buffer,
340                     (yield I.minibuffer.read($prompt = "Apropos command:",
341                                              $history = "apropos")),
342                     OPEN_NEW_BUFFER);
344 function apropos_command_new_window (I) {
345     apropos_command(I.buffer,
346                     (yield I.minibuffer.read($prompt = "Apropos command:",
347                                              $history = "apropos")),
348                     OPEN_NEW_WINDOW);
350 interactive("apropos-command", "List commands whose names contain a given substring.",
351             alternates(apropos_command_new_buffer, apropos_command_new_window));
355 define_keywords("$command", "$bindings");
356 function describe_command_buffer (window, element) {
357     this.constructor_begin();
358     keywords(arguments);
359     special_buffer.call(this, window, element, forward_keywords(arguments));
360     this.bindings = arguments.$bindings;
361     this.command = arguments.$command;
362     this.cmd = interactive_commands.get(this.command);
363     this.source_code_reference = this.cmd.source_code_reference;
364     this.constructor_end();
367 describe_command_buffer.prototype = {
369     get keymap() {
370         return help_buffer_keymap;
371     },
373     get title() { return "Command help: " + this.command; },
375     description : "*help*",
377     generate : function () {
378         var d = this.document;
380         var g = new help_document_generator(d, this);
382         g.add_help_stylesheet();
383         d.body.setAttribute("class", "describe-command");
385         var p;
387         p = g.element("p", d.body);
388         g.command_reference(this.command, p);
389         var cmd = interactive_commands.get(this.command);
390         if (cmd.source_code_reference)  {
391             g.text(" is an interactive command in ", p);
392             g.source_code_reference(cmd.source_code_reference, p);
393             g.text(".", p);
394         } else {
395             g.text(" is an interactive command.", p);
396         }
398         if (this.bindings.length > 0) {
399             p = g.element("p", d.body);
400             g.text("It is bound to ", p);
401             for (var i = 0; i < this.bindings.length; ++i) {
402                 if (i != 0)
403                     g.text(", ", p);
404                 g.key_binding(this.bindings[i], p);
405             }
406             g.text(".", p);
407         }
409         if (cmd.doc != null)
410             g.help_text(cmd.doc, d.body);
411     },
413     __proto__: special_buffer.prototype
417 function describe_command (buffer, command, target) {
418     var bindings = find_command_in_keymap(buffer, command);
419     create_buffer(buffer.window,
420                   buffer_creator(describe_command_buffer,
421                                  $opener = buffer,
422                                  $command = command,
423                                  $bindings = bindings),
424                   target);
426 function describe_command_new_buffer (I) {
427     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
428                      OPEN_NEW_BUFFER);
430 function describe_command_new_window (I) {
431     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
432                      OPEN_NEW_WINDOW);
434 interactive("describe-command", null,
435             alternates(describe_command_new_buffer, describe_command_new_window));
439 function view_referenced_source_code (buffer) {
440     if (buffer.source_code_reference == null)
441         throw interactive_error("Command not valid in current buffer.");
442     yield buffer.source_code_reference.open_in_editor();
444 interactive("view-referenced-source-code", null,
445             function (I) {yield view_referenced_source_code(I.buffer);});
448 define_keywords("$binding", "$other_bindings", "$key_sequence");
449 function describe_key_buffer (window, element) {
450     this.constructor_begin();
451     keywords(arguments);
452     special_buffer.call(this, window, element, forward_keywords(arguments));
453     this.key_sequence = arguments.$key_sequence;
454     this.bindings = arguments.$other_bindings;
455     this.bind = arguments.$binding;
456     this.source_code_reference = this.bind.source_code_reference;
457     this.constructor_end();
460 describe_key_buffer.prototype = {
462     get keymap() {
463         return help_buffer_keymap;
464     },
466     get title() { return "Key help: " + this.key_sequence; },
468     description : "*help*",
470     generate : function () {
471         var d = this.document;
473         var g = new help_document_generator(d, this);
475         g.add_help_stylesheet();
476         d.body.setAttribute("class", "describe-key");
478         var p;
480         p = g.element("p", d.body);
481         g.key_binding(this.key_sequence, p);
482         g.text(" is bound to the command ", p);
483         var command = this.bind.command;
484         if (command == null)
485             g.command_name("[pass through]", p);
486         else
487             g.command_reference(command, p);
488         if (this.bind.browser_object != null) {
489             g.text(" with the browser object, ", p);
490             if (this.bind.browser_object instanceof Function) {
491                 g.text("<anonymous browser-object function>", p);
492             } else if (this.bind.browser_object instanceof browser_object_class) {
493                 g.text(this.bind.browser_object.name, p);
494             } else if (typeof(this.bind.browser_object) == "string") {
495                 g.text('"'+this.bind.browser_object+'"', p);
496             } else {
497                 g.text(this.bind.browser_object, p);
498             }
499         }
500         if (this.source_code_reference) {
501             g.text(" in ", p);
502             g.source_code_reference(this.source_code_reference, p);
503         }
504         g.text(".", p);
506         if (command != null) {
507             p = g.element("p", d.body);
508             g.command_reference(command, p);
509             var cmd = interactive_commands.get(command);
510             if (cmd.source_code_reference)  {
511                 g.text(" is an interactive command in ", p);
512                 g.source_code_reference(cmd.source_code_reference, p);
513                 g.text(".", p);
514             } else {
515                 g.text(" is an interactive command.", p);
516             }
518             if (this.bindings.length > 0) {
519                 p = g.element("p", d.body);
520                 g.text("It is bound to ", p);
521                 for (var i = 0; i < this.bindings.length; ++i) {
522                     if (i != 0)
523                         g.text(", ", p);
524                     g.key_binding(this.bindings[i], p);
525                 }
526                 g.text(".", p);
527             }
529             if (cmd.doc != null)
530                 g.help_text(cmd.doc, d.body);
531         }
532     },
534     __proto__: special_buffer.prototype
538 function describe_key (buffer, key_info, target) {
539     var bindings;
540     var seq = key_info[0];
541     var bind = key_info[1];
543     if (bind.command)
544         bindings = find_command_in_keymap(buffer, bind.command);
545     else
546         bindings = [];
548     create_buffer(buffer.window,
549                   buffer_creator(describe_key_buffer,
550                                  $opener = buffer,
551                                  $key_sequence = seq.join(" "),
552                                  $other_bindings = bindings,
553                                  $binding = bind),
554                   target);
556 function describe_key_new_buffer (I) {
557     describe_key(I.buffer,
558                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
559                  OPEN_NEW_BUFFER);
561 function describe_key_new_window (I) {
562     describe_key(I.buffer,
563                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
564                  OPEN_NEW_WINDOW);
567 function describe_key_briefly (buffer, key_info) {
568     var bindings;
569     var seq = key_info[0];
570     var bind = key_info[1];
571     var browser_object = "";
572     if (bind.browser_object != null) {
573         browser_object += " on the browser object, ";
574         if (bind.browser_object instanceof Function) {
575             browser_object += "<anonymous browser-object function>";
576         } else if (bind.browser_object instanceof browser_object_class) {
577             browser_object += bind.browser_object.name;
578         } else if (typeof(bind.browser_object) == "string") {
579             browser_object += '"'+bind.browser_object+'"';
580         } else {
581             browser_object += bind.browser_object;
582         }
583     }
584     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command + browser_object);
587 interactive("describe-key", null,
588             alternates(describe_key_new_buffer, describe_key_new_window));
590 interactive("describe-key-briefly", null,
591     function (I) {
592         describe_key_briefly(
593             I.buffer,
594             (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
595     });
600 define_keywords("$variable");
601 function describe_variable_buffer (window, element) {
602     this.constructor_begin();
603     keywords(arguments);
604     special_buffer.call(this, window, element, forward_keywords(arguments));
605     this.variable = arguments.$variable;
606     this.cmd = user_variables[this.variable];
607     this.source_code_reference = this.cmd.source_code_reference;
608     this.constructor_end();
611 function pretty_print_value (value) {
612     if (value === undefined)
613         return "undefined";
614     if (value === null)
615         return "null";
616     if (typeof(value) == "object")
617         return value.toSource();
618     if (typeof(value) == "function")
619         return value.toString();
620     if (typeof(value) == "string") {
621         let s = value.toSource();
622         // toSource returns: (new String("<blah>"))
623         // we want just: "<blah>"
624         return s.substring(12, s.length - 2);
625     }
626     return new String(value);
629 describe_variable_buffer.prototype = {
631     get keymap() {
632         return help_buffer_keymap;
633     },
635     get title() { return "Variable help: " + this.variable; },
637     description : "*help*",
639     generate : function () {
640         var d = this.document;
642         var g = new help_document_generator(d, this);
644         g.add_help_stylesheet();
645         d.body.setAttribute("class", "describe-variable");
647         var p;
649         p = g.element("p", d.body);
650         g.variable_reference(this.variable, p);
651         var uvar = user_variables[this.variable];
652         if (uvar.source_code_reference)  {
653             g.text(" is a user variable in ", p);
654             g.source_code_reference(uvar.source_code_reference, p);
655             g.text(".", p);
656         } else {
657             g.text(" is a user variable.", p);
658         }
660         p = g.element("p", d.body);
661         g.text("Its value is: ", p);
662         let value = conkeror[this.variable];
663         {
664             let s = pretty_print_value(value);
665             let pre = g.element("pre", p);
666             g.text(s, pre);
667         }
669         if (uvar.doc != null)
670             g.help_text(uvar.doc, d.body);
672         if (uvar.default_value !== undefined &&
673             (uvar.default_value !== value ||
674              (typeof(uvar.default_value) != "object")))  {
675             p = g.element("p", d.body);
676             g.text("Its default value is: ", p);
677             {
678                 let s = pretty_print_value(uvar.default_value);
679                 let pre = g.element("pre", p);
680                 g.text(s, pre);
681             }
682         }
683     },
685     __proto__: special_buffer.prototype
689 function describe_variable (buffer, variable, target) {
690     create_buffer(buffer.window,
691                   buffer_creator(describe_variable_buffer,
692                                  $opener = buffer,
693                                  $variable = variable),
694                   target);
696 function describe_variable_new_buffer (I) {
697     describe_variable(I.buffer,
698                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
699                       OPEN_NEW_BUFFER);
701 function describe_variable_new_window (I) {
702     describe_variable(I.buffer,
703                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
704                       OPEN_NEW_WINDOW);
706 interactive("describe-variable", null,
707             alternates(describe_variable_new_buffer, describe_variable_new_window));
711 function describe_preference (buffer, preference, target) {
712     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
713     let url = "http://kb.mozillazine.org/" + key;
714     browser_object_follow(buffer, target, url);
716 function describe_preference_new_buffer (I) {
717     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
718                         OPEN_NEW_BUFFER);
720 function describe_preference_new_window (I) {
721     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
722                         OPEN_NEW_WINDOW);
724 interactive("describe-preference", null,
725             alternates(describe_preference_new_buffer, describe_preference_new_window));