generate_hints: check for doc.documentElement
[conkeror.git] / modules / help.js
blob232700021fbac4f215879e609ee2be2125203ba9
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2008 Nelson Elhage
4  * (C) Copyright 2008 David Glasser
5  * (C) Copyright 2009 John J. Foerch
6  *
7  * Use, modification, and distribution are subject to the terms specified in the
8  * COPYING file.
9 **/
11 in_module(null);
13 require("special-buffer.js");
14 require("interactive.js");
16 function where_is_command (buffer, command) {
17     var keymaps = get_current_keymaps(buffer.window);
18     var list = keymap_lookup_command(keymaps, command);
19     var msg;
20     if (list.length == 0)
21         msg = command + " is not on any key";
22     else
23         msg = command + " is on " + list.join(", ");
24     buffer.window.minibuffer.message(msg);
26 interactive("where-is", null, function (I) {
27     where_is_command(I.buffer,
28                      (yield I.minibuffer.read_command($prompt = "Where is command:")));
29 });
31 function help_document_generator (document, buffer) {
32     dom_generator.call(this, document, XHTML_NS);
33     this.buffer = buffer;
35 help_document_generator.prototype = {
36     constructor: help_document_generator,
37     __proto__: dom_generator.prototype,
39     key_binding: function (str, parent) {
40         var node = this.element("span", "class", "key-binding");
41         this.text(str, node);
42         if (parent)
43             parent.appendChild(node);
44         return node;
45     },
47     source_code_reference: function (ref, parent) {
48         var f = this.document.createDocumentFragment();
49         var module_name = ref.module_name;
50         var buffer = this.buffer;
51         //f.appendChild(this.text(module_name != null ? "module " : "file "));
52         var x = this.element("a",
53                              "class", "source-code-reference",
54                              "href", "javascript:");
55         x.addEventListener("click", function (event) {
56             co_call(function () {
57                 try {
58                     yield ref.open_in_editor();
59                 } catch (e) {
60                     handle_interactive_error(buffer.window, e);
61                 }}());
62             event.preventDefault();
63             event.stopPropagation();
64         }, false /* capture */);
65         x.textContent = (module_name != null ? module_name : ref.file_name);
66         f.appendChild(x);
67         if (parent)
68             parent.appendChild(f);
69         return f;
70     },
72     command_name: function (name, parent) {
73         var node = this.element("span", "class", "command");
74         this.text(name, node);
75         if (parent)
76             parent.appendChild(node);
77         return node;
78     },
80     command_reference: function (name, parent) {
81         var node = this.element("a",
82                                 "class", "command",
83                                 "href", "javascript:");
84         var buffer = this.buffer;
85         node.addEventListener("click", function (event) {
86                                   /* FIXME: don't hardcode browse target */
87                                   describe_command(buffer, name, OPEN_NEW_BUFFER);
88                                   event.preventDefault();
89                                   event.stopPropagation();
90             }, false /* capture */);
91         this.text(name, node);
92         if (parent)
93             parent.appendChild(node);
94         return node;
95     },
97     variable_reference: function (name, parent) {
98         var node = this.element("a", "class", "variable", "href", "#");
99         /* FIXME: make this work */
100         this.text(name, node);
101         if (parent)
102             parent.appendChild(node);
103         return node;
104     },
106     help_text: function (str, parent) {
107         var paras = str.split("\n");
108         var f = this.document.createDocumentFragment();
109         for (var i = 0; i < paras.length; ++i) {
110             var para = paras[i];
111             if (para.length == 0)
112                 continue;
114             var p = this.element("p", f);
116             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
118             var match;
119             var last_index = 0;
120             while ((match = regexp.exec(para)) != null) {
121                 this.text(para.substring(last_index, match.index), p);
122                 var command = match[1];
123                 /* FIXME: check if it is a valid command */
124                 this.command_reference(command, p);
125                 last_index = regexp.lastIndex;
126             }
127             if (last_index < para.length)
128                 this.text(para.substring(last_index), p);
129         }
130         if (parent != null)
131             parent.appendChild(f);
132         return f;
133     },
135     add_help_stylesheet: function () {
136         this.add_stylesheet("chrome://conkeror-gui/content/help.css");
137     }
141 function help_buffer_modality (buffer, element) {
142     buffer.keymaps.push(help_buffer_keymap);
146  * Describe Bindings
147  */
149 define_keywords("$binding_list");
150 function describe_bindings_buffer (window) {
151     this.constructor_begin();
152     keywords(arguments);
153     special_buffer.call(this, window, forward_keywords(arguments));
154     this.binding_list = arguments.$binding_list;
155     this.modalities.push(help_buffer_modality);
156     this.constructor_end();
158 describe_bindings_buffer.prototype = {
159     constructor: describe_bindings_buffer,
160     title: "Key bindings",
162     description: "*bindings*",
164     generate: function () {
165         var d = this.document;
166         var list = this.binding_list;
167         delete this.binding_list;
169         var list_by_keymap = {};
170         var keymap_list = [];
171         for each (let x in list) {
172             let name = x.bound_in || "";
173             let km;
174             if (name in list_by_keymap)
175                 km = list_by_keymap[name];
176             else {
177                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
178                 keymap_list.push(km);
179             }
180             let catname = x.category || "";
181             let cat;
182             if (catname in km.list_by_category)
183                 cat = km.list_by_category[catname];
184             else {
185                 cat = km.list_by_category[catname] = [];
186                 cat.name = catname;
187                 if (catname == "")
188                     km.category_list.unshift(cat);
189                 else
190                     km.category_list.push(cat);
191             }
192             cat.push(x);
193         }
195         var g = new help_document_generator(d, this);
196         g.add_help_stylesheet();
198         d.body.setAttribute("class", "help-list");
200         for each (let km in keymap_list) {
201             g.text(km.name, g.element("h1", d.body));
202             for each (let cat in km.category_list) {
203                 if (cat.name != "")
204                     g.text(cat.name, g.element("h2", d.body));
206                 let table = g.element("table", d.body);
207                 for (var i = 0; i < cat.length; ++i) {
208                     let bind = cat[i];
209                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
210                     let seq_td = g.element("td", tr, "class", "key-binding");
211                     g.text(bind.seq, seq_td);
212                     let command_td = g.element("td", tr, "class", "command");
213                     let help_str = null;
214                     if (bind.command != null) {
215                         if (typeof(bind.command) == "function") {
216                             g.text("[function]", command_td);
217                         } else {
218                             let cmd = interactive_commands.get(bind.command);
219                             if (cmd != null) {
220                                 g.command_reference(cmd.name, command_td);
221                                 help_str = cmd.shortdoc;
222                             } else {
223                                 g.text(bind.command, command_td);
224                             }
225                         }
226                     } else if (bind.keymap != null) {
227                         g.text("["+bind.keymap+"]", command_td);
228                     } else if (bind.fallthrough)
229                         g.text("[pass through]", command_td);
230                     let help_td = g.element("td", tr, "class", "help");
231                     g.text(help_str || "", help_td);
232                 }
233             }
234         }
235     },
237     __proto__: special_buffer.prototype
241 function describe_bindings (buffer, target, keymaps, prefix) {
242     var list = [];
243     if (! keymaps)
244         keymaps = get_current_keymaps(buffer.window);
245     if (prefix)
246         prefix = format_binding_sequence(
247             prefix.map(function (x) { return {key:x}; }))+" ";
248     else
249         prefix = "";
250     for_each_key_binding(keymaps, function (binding_stack) {
251             var last = binding_stack[binding_stack.length - 1];
252             //we don't care about auto-generated keymap bindings.
253             if (last.keymap && last.keymap.anonymous)
254                 return;
255             let bound_in = null;
256         outer:
257             for (let i = binding_stack.length - 1; i >= 0; --i) {
258                 bound_in = binding_stack[i].bound_in;
259                 while (bound_in) {
260                     if (bound_in.name && !bound_in.anonymous)
261                         break outer;
262                     bound_in = bound_in.bound_in;
263                 }
264             }
265             var keymap = null;
266             if (last.keymap && ! last.keymap.anonymous)
267                 keymap = last.keymap.name;
268             var bind = {seq: prefix+format_binding_sequence(binding_stack),
269                         fallthrough: last.fallthrough,
270                         command: last.command,
271                         keymap: keymap,
272                         bound_in: bound_in.name,
273                         category: last.category
274                        };
275             list.push(bind);
276         });
277     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
278                                                 $opener = buffer,
279                                                 $binding_list = list),
280                   target);
282 function describe_bindings_new_buffer (I) {
283     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
285 function describe_bindings_new_window (I) {
286     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
288 interactive("describe-bindings",
289     "Show a help buffer describing the bindings in the context keymaps, "+
290     "meaning the top-level keymaps according to the focus context in the "+
291     "current buffer.",
292     alternates(describe_bindings_new_buffer,
293                describe_bindings_new_window));
295 function describe_active_bindings_new_buffer (I) {
296     describe_bindings(I.buffer, OPEN_NEW_BUFFER,
297                       I.keymaps || get_current_keymaps(I.buffer.window),
298                       I.key_sequence.slice(0, -1));
300 function describe_active_bindings_new_window (I) {
301     describe_bindings(I.buffer, OPEN_NEW_WINDOW,
302                       I.keymaps || get_current_keymaps(I.buffer.window),
303                       I.key_sequence.slice(0, -1));
305 interactive("describe-active-bindings",
306     "Show a help buffer describing the bindings in the active keymaps, "+
307     "meaning the keymaps in the middle of an ongoing key sequence.  This "+
308     "command is intended to be called via `sequence_help_keymap'.  For "+
309     "that reason, `describe-active-bindings' does not consume and prefix "+
310     "commands like `universal-argument', as doing so would lead to "+
311     "ambiguities with respect to the intent of the user.",
312     describe_active_bindings_new_buffer);
316  * Apropos Command
317  */
319 define_keywords("$command_list");
320 function apropos_command_buffer (window) {
321     this.constructor_begin();
322     keywords(arguments);
323     special_buffer.call(this, window, forward_keywords(arguments));
324     this.command_list = arguments.$command_list;
325     this.modalities.push(help_buffer_modality);
326     this.constructor_end();
328 apropos_command_buffer.prototype = {
329     constructor: apropos_command_buffer,
330     title: "Apropos commands",
332     description: "*Apropos*",
334     generate: function () {
335         var d = this.document;
336         var list = this.command_list;
337         delete this.command_list;
339         var g = new help_document_generator(d, this);
340         g.add_help_stylesheet();
342         d.body.setAttribute("class", "help-list");
344         var table = d.createElementNS(XHTML_NS, "table");
345         for (var i = 0; i < list.length; ++i) {
346             var binding = list[i];
347             var tr = d.createElementNS(XHTML_NS, "tr");
348             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
350             var command_td = d.createElementNS(XHTML_NS,"td");
351             g.command_reference(binding.name, command_td);
353             var shortdoc = "";
354             if (binding.cmd.shortdoc != null)
355                 shortdoc = binding.cmd.shortdoc;
356             tr.appendChild(command_td);
358             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
359             shortdoc_td.setAttribute("class", "help");
360             shortdoc_td.textContent = shortdoc;
361             tr.appendChild(shortdoc_td);
363             table.appendChild(tr);
364         }
365         d.body.appendChild(table);
366     },
368     __proto__: special_buffer.prototype
372 /* TODO: support regexps/etc. */
373 function apropos_command (buffer, substring, target) {
374     var list = [];
375     interactive_commands.for_each(function (name, cmd) {
376         if (name.indexOf(substring) != -1) {
377             var binding = {name: name, cmd: cmd};
378             list.push(binding);
379         }
380     });
381     list.sort(function (a,b) {
382                   if (a.name < b.name)
383                       return -1;
384                   if (a.name > b.name)
385                       return 1;
386                   return 0
387               });
388     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
389                                                 $opener = buffer,
390                                                 $command_list = list),
391                   target);
394 function apropos_command_new_buffer (I) {
395     apropos_command(I.buffer,
396                     (yield I.minibuffer.read($prompt = "Apropos command:",
397                                              $history = "apropos")),
398                     OPEN_NEW_BUFFER);
400 function apropos_command_new_window (I) {
401     apropos_command(I.buffer,
402                     (yield I.minibuffer.read($prompt = "Apropos command:",
403                                              $history = "apropos")),
404                     OPEN_NEW_WINDOW);
406 interactive("apropos-command", "List commands whose names contain a given substring.",
407             alternates(apropos_command_new_buffer, apropos_command_new_window));
412  * Describe Command
413  */
415 define_keywords("$command", "$bindings");
416 function describe_command_buffer (window) {
417     this.constructor_begin();
418     keywords(arguments);
419     special_buffer.call(this, window, forward_keywords(arguments));
420     this.bindings = arguments.$bindings;
421     this.command = arguments.$command;
422     this.cmd = interactive_commands.get(this.command);
423     this.source_code_reference = this.cmd.source_code_reference;
424     this.modalities.push(help_buffer_modality);
425     this.constructor_end();
427 describe_command_buffer.prototype = {
428     constructor: describe_command_buffer,
429     get title () { return "Command help: " + this.command; },
431     description: "*help*",
433     generate: function () {
434         var d = this.document;
436         var g = new help_document_generator(d, this);
438         g.add_help_stylesheet();
439         d.body.setAttribute("class", "describe-command");
441         var p;
443         p = g.element("p", d.body);
444         g.command_reference(this.command, p);
445         var cmd = interactive_commands.get(this.command);
446         if (cmd.source_code_reference)  {
447             g.text(" is an interactive command in ", p);
448             g.source_code_reference(cmd.source_code_reference, p);
449             g.text(".", p);
450         } else {
451             g.text(" is an interactive command.", p);
452         }
454         if (this.bindings.length > 0) {
455             p = g.element("p", d.body);
456             g.text("It is bound to ", p);
457             for (var i = 0; i < this.bindings.length; ++i) {
458                 if (i != 0)
459                     g.text(", ", p);
460                 g.key_binding(this.bindings[i], p);
461             }
462             g.text(".", p);
463         }
465         if (cmd.doc != null)
466             g.help_text(cmd.doc, d.body);
467     },
469     __proto__: special_buffer.prototype
473 function describe_command (buffer, command, target) {
474     var keymaps = get_current_keymaps(buffer.window);
475     var bindings = keymap_lookup_command(keymaps, command);
476     create_buffer(buffer.window,
477                   buffer_creator(describe_command_buffer,
478                                  $opener = buffer,
479                                  $command = command,
480                                  $bindings = bindings),
481                   target);
483 function describe_command_new_buffer (I) {
484     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
485                      OPEN_NEW_BUFFER);
487 function describe_command_new_window (I) {
488     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
489                      OPEN_NEW_WINDOW);
491 interactive("describe-command", null,
492             alternates(describe_command_new_buffer, describe_command_new_window));
496 function view_referenced_source_code (buffer) {
497     if (buffer.source_code_reference == null)
498         throw interactive_error("Command not valid in current buffer.");
499     yield buffer.source_code_reference.open_in_editor();
501 interactive("view-referenced-source-code", null,
502             function (I) {yield view_referenced_source_code(I.buffer);});
506  * Describe Key
507  */
509 define_keywords("$binding", "$other_bindings", "$key_sequence");
510 function describe_key_buffer (window) {
511     this.constructor_begin();
512     keywords(arguments);
513     special_buffer.call(this, window, forward_keywords(arguments));
514     this.key_sequence = arguments.$key_sequence;
515     this.bindings = arguments.$other_bindings;
516     this.bind = arguments.$binding;
517     this.source_code_reference = this.bind.source_code_reference;
518     this.modalities.push(help_buffer_modality);
519     this.constructor_end();
521 describe_key_buffer.prototype = {
522     constructor: describe_key_buffer,
523     get title () { return "Key help: " + this.key_sequence; },
525     description: "*help*",
527     generate: function () {
528         var d = this.document;
530         var g = new help_document_generator(d, this);
532         g.add_help_stylesheet();
533         d.body.setAttribute("class", "describe-key");
535         var p;
537         p = g.element("p", d.body);
538         g.key_binding(this.key_sequence, p);
539         g.text(" is bound to the command ", p);
540         var command = this.bind.command;
541         if (command == null)
542             g.command_name("[pass through]", p);
543         else
544             g.command_reference(command, p);
545         if (this.bind.browser_object != null) {
546             g.text(" with the browser object, ", p);
547             if (this.bind.browser_object instanceof Function) {
548                 g.text("<anonymous browser-object function>", p);
549             } else if (this.bind.browser_object instanceof browser_object_class) {
550                 g.text(this.bind.browser_object.name, p);
551             } else if (typeof(this.bind.browser_object) == "string") {
552                 g.text('"'+this.bind.browser_object+'"', p);
553             } else {
554                 g.text(this.bind.browser_object, p);
555             }
556         }
557         if (this.source_code_reference) {
558             g.text(" in ", p);
559             g.source_code_reference(this.source_code_reference, p);
560         }
561         g.text(".", p);
563         if (command != null) {
564             p = g.element("p", d.body);
565             g.command_reference(command, p);
566             var cmd = interactive_commands.get(command);
567             if (cmd.source_code_reference)  {
568                 g.text(" is an interactive command in ", p);
569                 g.source_code_reference(cmd.source_code_reference, p);
570                 g.text(".", p);
571             } else {
572                 g.text(" is an interactive command.", p);
573             }
575             if (this.bindings.length > 0) {
576                 p = g.element("p", d.body);
577                 g.text("It is bound to ", p);
578                 for (var i = 0; i < this.bindings.length; ++i) {
579                     if (i != 0)
580                         g.text(", ", p);
581                     g.key_binding(this.bindings[i], p);
582                 }
583                 g.text(".", p);
584             }
586             if (cmd.doc != null)
587                 g.help_text(cmd.doc, d.body);
588         }
589     },
591     __proto__: special_buffer.prototype
595 function describe_key (buffer, key_info, target) {
596     var bindings;
597     var seq = key_info[0];
598     var bind = key_info[1];
599     var keymaps = get_current_keymaps(buffer.window);
600     if (bind.command)
601         bindings = keymap_lookup_command(keymaps, bind.command);
602     else
603         bindings = [];
604     create_buffer(buffer.window,
605                   buffer_creator(describe_key_buffer,
606                                  $opener = buffer,
607                                  $key_sequence = seq.join(" "),
608                                  $other_bindings = bindings,
609                                  $binding = bind),
610                   target);
612 function describe_key_new_buffer (I) {
613     describe_key(I.buffer,
614                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:")),
615                  OPEN_NEW_BUFFER);
617 function describe_key_new_window (I) {
618     describe_key(I.buffer,
619                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:")),
620                  OPEN_NEW_WINDOW);
623 function describe_key_briefly (buffer, key_info) {
624     var bindings;
625     var seq = key_info[0];
626     var bind = key_info[1];
627     var browser_object = "";
628     if (bind.browser_object != null) {
629         browser_object += " on the browser object, ";
630         if (bind.browser_object instanceof Function) {
631             browser_object += "<anonymous browser-object function>";
632         } else if (bind.browser_object instanceof browser_object_class) {
633             browser_object += bind.browser_object.name;
634         } else if (typeof(bind.browser_object) == "string") {
635             browser_object += '"'+bind.browser_object+'"';
636         } else {
637             browser_object += bind.browser_object;
638         }
639     }
640     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command + browser_object);
643 interactive("describe-key", null,
644             alternates(describe_key_new_buffer, describe_key_new_window));
646 interactive("describe-key-briefly", null,
647     function (I) {
648         describe_key_briefly(
649             I.buffer,
650             (yield I.minibuffer.read_key_binding($prompt = "Describe key:")));
651     });
656  * Describe Variable
657  */
659 define_keywords("$variable");
660 function describe_variable_buffer (window) {
661     this.constructor_begin();
662     keywords(arguments);
663     special_buffer.call(this, window, forward_keywords(arguments));
664     this.variable = arguments.$variable;
665     this.cmd = user_variables[this.variable];
666     this.source_code_reference = this.cmd.source_code_reference;
667     this.modalities.push(help_buffer_modality);
668     this.constructor_end();
670 describe_variable_buffer.prototype = {
671     constructor: describe_variable_buffer,
672     get title () { return "Variable help: " + this.variable; },
674     description: "*help*",
676     generate: function () {
677         var d = this.document;
679         var g = new help_document_generator(d, this);
681         g.add_help_stylesheet();
682         d.body.setAttribute("class", "describe-variable");
684         var p;
686         p = g.element("p", d.body);
687         g.variable_reference(this.variable, p);
688         var uvar = user_variables[this.variable];
689         if (uvar.source_code_reference)  {
690             g.text(" is a user variable in ", p);
691             g.source_code_reference(uvar.source_code_reference, p);
692             g.text(".", p);
693         } else {
694             g.text(" is a user variable.", p);
695         }
697         p = g.element("p", d.body);
698         g.text("Its value is: ", p);
699         let value = conkeror[this.variable];
700         {
701             let s = pretty_print_value(value);
702             let pre = g.element("pre", p);
703             g.text(s, pre);
704         }
706         if (uvar.doc != null)
707             g.help_text(uvar.doc, d.body);
709         if (uvar.default_value !== undefined &&
710             (uvar.default_value !== value ||
711              (typeof(uvar.default_value) != "object")))  {
712             p = g.element("p", d.body);
713             g.text("Its default value is: ", p);
714             {
715                 let s = pretty_print_value(uvar.default_value);
716                 let pre = g.element("pre", p);
717                 g.text(s, pre);
718             }
719         }
720     },
722     __proto__: special_buffer.prototype
726 function describe_variable (buffer, variable, target) {
727     create_buffer(buffer.window,
728                   buffer_creator(describe_variable_buffer,
729                                  $opener = buffer,
730                                  $variable = variable),
731                   target);
733 function describe_variable_new_buffer (I) {
734     describe_variable(I.buffer,
735                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
736                       OPEN_NEW_BUFFER);
738 function describe_variable_new_window (I) {
739     describe_variable(I.buffer,
740                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
741                       OPEN_NEW_WINDOW);
743 interactive("describe-variable", null,
744             alternates(describe_variable_new_buffer, describe_variable_new_window));
749  * Describe Preference
750  */
752 function describe_preference (buffer, preference, target) {
753     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
754     let url = "http://kb.mozillazine.org/" + key;
755     browser_object_follow(buffer, target, url);
757 function describe_preference_new_buffer (I) {
758     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
759                         OPEN_NEW_BUFFER);
761 function describe_preference_new_window (I) {
762     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
763                         OPEN_NEW_WINDOW);
765 interactive("describe-preference", null,
766             alternates(describe_preference_new_buffer, describe_preference_new_window));
768 provide("help");