pretty-print.js: new file for all pretty_print_* functions
[conkeror.git] / modules / help.js
blob1dfffee01d4c1bcacfaa45eda39ccace9fb950fe
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 require("special-buffer.js");
12 require("interactive.js");
14 function where_is_command (buffer, command) {
15     var keymaps = get_current_keymaps(buffer.window);
16     var list = keymap_lookup_command(keymaps, command);
17     var msg;
18     if (list.length == 0)
19         msg = command + " is not on any key";
20     else
21         msg = command + " is on " + list.join(", ");
22     buffer.window.minibuffer.message(msg);
24 interactive("where-is", null, function (I) {
25     where_is_command(I.buffer,
26                      (yield I.minibuffer.read_command($prompt = "Where is command:")));
27 });
29 function help_document_generator (document, buffer) {
30     dom_generator.call(this, document, XHTML_NS);
31     this.buffer = buffer;
33 help_document_generator.prototype = {
34     __proto__: dom_generator.prototype,
36     key_binding : function (str, parent) {
37         var node = this.element("span", "class", "key-binding");
38         this.text(str, node);
39         if (parent)
40             parent.appendChild(node);
41         return node;
42     },
44     source_code_reference : function (ref, parent) {
45         var f = this.document.createDocumentFragment();
46         var module_name = ref.module_name;
47         var buffer = this.buffer;
48         //f.appendChild(this.text(module_name != null ? "module " : "file "));
49         var x = this.element("a",
50                              "class", "source-code-reference",
51                              "href", "javascript:");
52         x.addEventListener("click", function (event) {
53             co_call(function () {
54                 try {
55                     yield ref.open_in_editor();
56                 } catch (e) {
57                     handle_interactive_error(buffer.window, e);
58                 }}());
59             event.preventDefault();
60             event.stopPropagation();
61         }, false /* capture */);
62         x.textContent = (module_name != null ? module_name : ref.file_name);
63         f.appendChild(x);
64         if (parent)
65             parent.appendChild(f);
66         return f;
67     },
69     command_name : function (name, parent) {
70         var node = this.element("span", "class", "command");
71         this.text(name, node);
72         if (parent)
73             parent.appendChild(node);
74         return node;
75     },
77     command_reference : function (name, parent) {
78         var node = this.element("a",
79                                 "class", "command",
80                                 "href", "javascript:");
81         var buffer = this.buffer;
82         node.addEventListener("click", function (event) {
83                                   /* FIXME: don't hardcode browse target */
84                                   describe_command(buffer, name, OPEN_NEW_BUFFER);
85                                   event.preventDefault();
86                                   event.stopPropagation();
87             }, false /* capture */);
88         this.text(name, node);
89         if (parent)
90             parent.appendChild(node);
91         return node;
92     },
94     variable_reference : function (name, parent) {
95         var node = this.element("a", "class", "variable", "href", "#");
96         /* FIXME: make this work */
97         this.text(name, node);
98         if (parent)
99             parent.appendChild(node);
100         return node;
101     },
103     help_text : function (str, parent) {
104         var paras = str.split("\n");
105         var f = this.document.createDocumentFragment();
106         for (var i = 0; i < paras.length; ++i) {
107             var para = paras[i];
108             if (para.length == 0)
109                 continue;
111             var p = this.element("p", f);
113             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
115             var match;
116             var last_index = 0;
117             while ((match = regexp.exec(para)) != null) {
118                 this.text(para.substring(last_index, match.index), p);
119                 var command = match[1];
120                 /* FIXME: check if it is a valid command */
121                 this.command_reference(command, p);
122                 last_index = regexp.lastIndex;
123             }
124             if (last_index < para.length)
125                 this.text(para.substring(last_index), p);
126         }
127         if (parent != null)
128             parent.appendChild(f);
129         return f;
130     },
132     add_help_stylesheet : function () {
133         this.add_stylesheet("chrome://conkeror-gui/content/help.css");
134     }
138 function help_buffer_modality (buffer, element) {
139     buffer.keymaps.push(help_buffer_keymap);
143  * Describe Bindings
144  */
146 define_keywords("$binding_list");
147 function describe_bindings_buffer (window, element) {
148     this.constructor_begin();
149     keywords(arguments);
150     special_buffer.call(this, window, element, forward_keywords(arguments));
151     this.binding_list = arguments.$binding_list;
152     this.modalities.push(help_buffer_modality);
153     this.constructor_end();
156 describe_bindings_buffer.prototype = {
157     title : "Key bindings",
159     description : "*bindings*",
161     generate : function () {
162         var d = this.document;
163         var list = this.binding_list;
164         delete this.binding_list;
166         var list_by_keymap = {};
167         var keymap_list = [];
168         for each (let x in list) {
169             let name = x.bound_in || "";
170             let km;
171             if (name in list_by_keymap)
172                 km = list_by_keymap[name];
173             else {
174                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
175                 keymap_list.push(km);
176             }
177             let catname = x.category || "";
178             let cat;
179             if (catname in km.list_by_category)
180                 cat = km.list_by_category[catname];
181             else {
182                 cat = km.list_by_category[catname] = [];
183                 cat.name = catname;
184                 if (catname == "")
185                     km.category_list.unshift(cat);
186                 else
187                     km.category_list.push(cat);
188             }
189             cat.push(x);
190         }
192         var g = new help_document_generator(d, this);
193         g.add_help_stylesheet();
195         d.body.setAttribute("class", "help-list");
197         for each (let km in keymap_list) {
198             g.text(km.name, g.element("h1", d.body));
199             for each (let cat in km.category_list) {
200                 if (cat.name != "")
201                     g.text(cat.name, g.element("h2", d.body));
203                 let table = g.element("table", d.body);
204                 for (var i = 0; i < cat.length; ++i) {
205                     let bind = cat[i];
206                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
207                     let seq_td = g.element("td", tr, "class", "key-binding");
208                     g.text(bind.seq, seq_td);
209                     let command_td = g.element("td", tr, "class", "command");
210                     let help_str = null;
211                     if (bind.command != null) {
212                         if (typeof(bind.command) == "function") {
213                             g.text("[function]", command_td);
214                         } else {
215                             g.text(bind.command, command_td);
216                             let cmd = interactive_commands.get(bind.command);
217                             if (cmd != null)
218                                 help_str = cmd.shortdoc;
219                         }
220                     } else if (bind.fallthrough)
221                         g.text("[pass through]", command_td);
222                     let help_td = g.element("td", tr, "class", "help");
223                     g.text(help_str || "", help_td);
224                 }
225             }
226         }
227     },
229     __proto__: special_buffer.prototype
233 function describe_bindings (buffer, target, keymaps, prefix) {
234     var list = [];
235     if (! keymaps)
236         keymaps = get_current_keymaps(buffer.window);
237     if (prefix)
238         prefix = format_binding_sequence(
239             prefix.map(function (x) { return {key:x}; }))+" ";
240     else
241         prefix = "";
242     for_each_key_binding(keymaps, function (binding_stack) {
243             var last = binding_stack[binding_stack.length - 1];
244             if (last.command == null && !last.fallthrough)
245                 return;
246             let bound_in = null;
247         outer:
248             for (let i = binding_stack.length - 1; i >= 0; --i) {
249                 bound_in = binding_stack[i].bound_in;
250                 while (bound_in) {
251                     if (bound_in.name && !bound_in.anonymous)
252                         break outer;
253                     bound_in = bound_in.bound_in;
254                 }
255             }
256             var bind = {seq: prefix+format_binding_sequence(binding_stack),
257                         fallthrough: last.fallthrough,
258                         command: last.command,
259                         bound_in: bound_in.name,
260                         category: last.category
261                        };
262             list.push(bind);
263         });
264     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
265                                                 $opener = buffer,
266                                                 $binding_list = list),
267                   target);
269 function describe_bindings_new_buffer (I) {
270     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
272 function describe_bindings_new_window (I) {
273     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
275 interactive("describe-bindings",
276     "Show a help buffer describing the bindings in the context keymaps, "+
277     "meaning the top-level keymaps according to the focus context in the "+
278     "current buffer.",
279     alternates(describe_bindings_new_buffer,
280                describe_bindings_new_window));
282 function describe_active_bindings_new_buffer (I) {
283     describe_bindings(I.buffer, OPEN_NEW_BUFFER,
284                       I.keymaps || get_current_keymaps(I.buffer.window),
285                       I.key_sequence.slice(0, -1));
287 function describe_active_bindings_new_window (I) {
288     describe_bindings(I.buffer, OPEN_NEW_WINDOW,
289                       I.keymaps || get_current_keymaps(I.buffer.window),
290                       I.key_sequence.slice(0, -1));
292 interactive("describe-active-bindings",
293     "Show a help buffer describing the bindings in the active keymaps, "+
294     "meaning the keymaps in the middle of an ongoing key sequence.  This "+
295     "command is intended to be called via `sequence_help_keymap'.  For "+
296     "that reason, `describe-active-bindings' does not consume and prefix "+
297     "commands like `universal-argument', as doing so would lead to "+
298     "ambiguities with respect to the intent of the user.",
299     describe_active_bindings_new_buffer);
303  * Apropos Command
304  */
306 define_keywords("$command_list");
307 function apropos_command_buffer (window, element) {
308     this.constructor_begin();
309     keywords(arguments);
310     special_buffer.call(this, window, element, forward_keywords(arguments));
311     this.command_list = arguments.$command_list;
312     this.modalities.push(help_buffer_modality);
313     this.constructor_end();
316 apropos_command_buffer.prototype = {
317     title : "Apropos commands",
319     description : "*Apropos*",
321     generate : function () {
322         var d = this.document;
323         var list = this.command_list;
324         delete this.command_list;
326         var g = new help_document_generator(d, this);
327         g.add_help_stylesheet();
329         d.body.setAttribute("class", "help-list");
331         var table = d.createElementNS(XHTML_NS, "table");
332         for (var i = 0; i < list.length; ++i) {
333             var binding = list[i];
334             var tr = d.createElementNS(XHTML_NS, "tr");
335             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
337             var command_td = d.createElementNS(XHTML_NS,"td");
338             g.command_reference(binding.name, command_td);
340             var shortdoc = "";
341             if (binding.cmd.shortdoc != null)
342                 shortdoc = binding.cmd.shortdoc;
343             tr.appendChild(command_td);
345             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
346             shortdoc_td.setAttribute("class", "help");
347             shortdoc_td.textContent = shortdoc;
348             tr.appendChild(shortdoc_td);
350             table.appendChild(tr);
351         }
352         d.body.appendChild(table);
353     },
355     __proto__: special_buffer.prototype
359 /* TODO: support regexps/etc. */
360 function apropos_command (buffer, substring, target) {
361     var list = [];
362     interactive_commands.for_each(function (name, cmd) {
363         if (name.indexOf(substring) != -1) {
364             var binding = {name: name, cmd: cmd};
365             list.push(binding);
366         }
367     });
368     list.sort(function (a,b) {
369                   if (a.name < b.name)
370                       return -1;
371                   if (a.name > b.name)
372                       return 1;
373                   return 0
374               });
375     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
376                                                 $opener = buffer,
377                                                 $command_list = list),
378                   target);
381 function apropos_command_new_buffer (I) {
382     apropos_command(I.buffer,
383                     (yield I.minibuffer.read($prompt = "Apropos command:",
384                                              $history = "apropos")),
385                     OPEN_NEW_BUFFER);
387 function apropos_command_new_window (I) {
388     apropos_command(I.buffer,
389                     (yield I.minibuffer.read($prompt = "Apropos command:",
390                                              $history = "apropos")),
391                     OPEN_NEW_WINDOW);
393 interactive("apropos-command", "List commands whose names contain a given substring.",
394             alternates(apropos_command_new_buffer, apropos_command_new_window));
399  * Describe Command
400  */
402 define_keywords("$command", "$bindings");
403 function describe_command_buffer (window, element) {
404     this.constructor_begin();
405     keywords(arguments);
406     special_buffer.call(this, window, element, forward_keywords(arguments));
407     this.bindings = arguments.$bindings;
408     this.command = arguments.$command;
409     this.cmd = interactive_commands.get(this.command);
410     this.source_code_reference = this.cmd.source_code_reference;
411     this.modalities.push(help_buffer_modality);
412     this.constructor_end();
415 describe_command_buffer.prototype = {
416     get title() { return "Command help: " + this.command; },
418     description : "*help*",
420     generate : function () {
421         var d = this.document;
423         var g = new help_document_generator(d, this);
425         g.add_help_stylesheet();
426         d.body.setAttribute("class", "describe-command");
428         var p;
430         p = g.element("p", d.body);
431         g.command_reference(this.command, p);
432         var cmd = interactive_commands.get(this.command);
433         if (cmd.source_code_reference)  {
434             g.text(" is an interactive command in ", p);
435             g.source_code_reference(cmd.source_code_reference, p);
436             g.text(".", p);
437         } else {
438             g.text(" is an interactive command.", p);
439         }
441         if (this.bindings.length > 0) {
442             p = g.element("p", d.body);
443             g.text("It is bound to ", p);
444             for (var i = 0; i < this.bindings.length; ++i) {
445                 if (i != 0)
446                     g.text(", ", p);
447                 g.key_binding(this.bindings[i], p);
448             }
449             g.text(".", p);
450         }
452         if (cmd.doc != null)
453             g.help_text(cmd.doc, d.body);
454     },
456     __proto__: special_buffer.prototype
460 function describe_command (buffer, command, target) {
461     var keymaps = get_current_keymaps(buffer.window);
462     var bindings = keymap_lookup_command(keymaps, command);
463     create_buffer(buffer.window,
464                   buffer_creator(describe_command_buffer,
465                                  $opener = buffer,
466                                  $command = command,
467                                  $bindings = bindings),
468                   target);
470 function describe_command_new_buffer (I) {
471     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
472                      OPEN_NEW_BUFFER);
474 function describe_command_new_window (I) {
475     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
476                      OPEN_NEW_WINDOW);
478 interactive("describe-command", null,
479             alternates(describe_command_new_buffer, describe_command_new_window));
483 function view_referenced_source_code (buffer) {
484     if (buffer.source_code_reference == null)
485         throw interactive_error("Command not valid in current buffer.");
486     yield buffer.source_code_reference.open_in_editor();
488 interactive("view-referenced-source-code", null,
489             function (I) {yield view_referenced_source_code(I.buffer);});
493  * Describe Key
494  */
496 define_keywords("$binding", "$other_bindings", "$key_sequence");
497 function describe_key_buffer (window, element) {
498     this.constructor_begin();
499     keywords(arguments);
500     special_buffer.call(this, window, element, forward_keywords(arguments));
501     this.key_sequence = arguments.$key_sequence;
502     this.bindings = arguments.$other_bindings;
503     this.bind = arguments.$binding;
504     this.source_code_reference = this.bind.source_code_reference;
505     this.modalities.push(help_buffer_modality);
506     this.constructor_end();
509 describe_key_buffer.prototype = {
510     get title() { return "Key help: " + this.key_sequence; },
512     description : "*help*",
514     generate : function () {
515         var d = this.document;
517         var g = new help_document_generator(d, this);
519         g.add_help_stylesheet();
520         d.body.setAttribute("class", "describe-key");
522         var p;
524         p = g.element("p", d.body);
525         g.key_binding(this.key_sequence, p);
526         g.text(" is bound to the command ", p);
527         var command = this.bind.command;
528         if (command == null)
529             g.command_name("[pass through]", p);
530         else
531             g.command_reference(command, p);
532         if (this.bind.browser_object != null) {
533             g.text(" with the browser object, ", p);
534             if (this.bind.browser_object instanceof Function) {
535                 g.text("<anonymous browser-object function>", p);
536             } else if (this.bind.browser_object instanceof browser_object_class) {
537                 g.text(this.bind.browser_object.name, p);
538             } else if (typeof(this.bind.browser_object) == "string") {
539                 g.text('"'+this.bind.browser_object+'"', p);
540             } else {
541                 g.text(this.bind.browser_object, p);
542             }
543         }
544         if (this.source_code_reference) {
545             g.text(" in ", p);
546             g.source_code_reference(this.source_code_reference, p);
547         }
548         g.text(".", p);
550         if (command != null) {
551             p = g.element("p", d.body);
552             g.command_reference(command, p);
553             var cmd = interactive_commands.get(command);
554             if (cmd.source_code_reference)  {
555                 g.text(" is an interactive command in ", p);
556                 g.source_code_reference(cmd.source_code_reference, p);
557                 g.text(".", p);
558             } else {
559                 g.text(" is an interactive command.", p);
560             }
562             if (this.bindings.length > 0) {
563                 p = g.element("p", d.body);
564                 g.text("It is bound to ", p);
565                 for (var i = 0; i < this.bindings.length; ++i) {
566                     if (i != 0)
567                         g.text(", ", p);
568                     g.key_binding(this.bindings[i], p);
569                 }
570                 g.text(".", p);
571             }
573             if (cmd.doc != null)
574                 g.help_text(cmd.doc, d.body);
575         }
576     },
578     __proto__: special_buffer.prototype
582 function describe_key (buffer, key_info, target) {
583     var bindings;
584     var seq = key_info[0];
585     var bind = key_info[1];
586     var keymaps = get_current_keymaps(buffer.window);
587     if (bind.command)
588         bindings = keymap_lookup_command(keymaps, bind.command);
589     else
590         bindings = [];
591     create_buffer(buffer.window,
592                   buffer_creator(describe_key_buffer,
593                                  $opener = buffer,
594                                  $key_sequence = seq.join(" "),
595                                  $other_bindings = bindings,
596                                  $binding = bind),
597                   target);
599 function describe_key_new_buffer (I) {
600     describe_key(I.buffer,
601                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
602                  OPEN_NEW_BUFFER);
604 function describe_key_new_window (I) {
605     describe_key(I.buffer,
606                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
607                  OPEN_NEW_WINDOW);
610 function describe_key_briefly (buffer, key_info) {
611     var bindings;
612     var seq = key_info[0];
613     var bind = key_info[1];
614     var browser_object = "";
615     if (bind.browser_object != null) {
616         browser_object += " on the browser object, ";
617         if (bind.browser_object instanceof Function) {
618             browser_object += "<anonymous browser-object function>";
619         } else if (bind.browser_object instanceof browser_object_class) {
620             browser_object += bind.browser_object.name;
621         } else if (typeof(bind.browser_object) == "string") {
622             browser_object += '"'+bind.browser_object+'"';
623         } else {
624             browser_object += bind.browser_object;
625         }
626     }
627     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command + browser_object);
630 interactive("describe-key", null,
631             alternates(describe_key_new_buffer, describe_key_new_window));
633 interactive("describe-key-briefly", null,
634     function (I) {
635         describe_key_briefly(
636             I.buffer,
637             (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
638     });
643  * Describe Variable
644  */
646 define_keywords("$variable");
647 function describe_variable_buffer (window, element) {
648     this.constructor_begin();
649     keywords(arguments);
650     special_buffer.call(this, window, element, forward_keywords(arguments));
651     this.variable = arguments.$variable;
652     this.cmd = user_variables[this.variable];
653     this.source_code_reference = this.cmd.source_code_reference;
654     this.modalities.push(help_buffer_modality);
655     this.constructor_end();
658 describe_variable_buffer.prototype = {
659     get title() { return "Variable help: " + this.variable; },
661     description : "*help*",
663     generate : function () {
664         var d = this.document;
666         var g = new help_document_generator(d, this);
668         g.add_help_stylesheet();
669         d.body.setAttribute("class", "describe-variable");
671         var p;
673         p = g.element("p", d.body);
674         g.variable_reference(this.variable, p);
675         var uvar = user_variables[this.variable];
676         if (uvar.source_code_reference)  {
677             g.text(" is a user variable in ", p);
678             g.source_code_reference(uvar.source_code_reference, p);
679             g.text(".", p);
680         } else {
681             g.text(" is a user variable.", p);
682         }
684         p = g.element("p", d.body);
685         g.text("Its value is: ", p);
686         let value = conkeror[this.variable];
687         {
688             let s = pretty_print_value(value);
689             let pre = g.element("pre", p);
690             g.text(s, pre);
691         }
693         if (uvar.doc != null)
694             g.help_text(uvar.doc, d.body);
696         if (uvar.default_value !== undefined &&
697             (uvar.default_value !== value ||
698              (typeof(uvar.default_value) != "object")))  {
699             p = g.element("p", d.body);
700             g.text("Its default value is: ", p);
701             {
702                 let s = pretty_print_value(uvar.default_value);
703                 let pre = g.element("pre", p);
704                 g.text(s, pre);
705             }
706         }
707     },
709     __proto__: special_buffer.prototype
713 function describe_variable (buffer, variable, target) {
714     create_buffer(buffer.window,
715                   buffer_creator(describe_variable_buffer,
716                                  $opener = buffer,
717                                  $variable = variable),
718                   target);
720 function describe_variable_new_buffer (I) {
721     describe_variable(I.buffer,
722                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
723                       OPEN_NEW_BUFFER);
725 function describe_variable_new_window (I) {
726     describe_variable(I.buffer,
727                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
728                       OPEN_NEW_WINDOW);
730 interactive("describe-variable", null,
731             alternates(describe_variable_new_buffer, describe_variable_new_window));
736  * Describe Preference
737  */
739 function describe_preference (buffer, preference, target) {
740     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
741     let url = "http://kb.mozillazine.org/" + key;
742     browser_object_follow(buffer, target, url);
744 function describe_preference_new_buffer (I) {
745     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
746                         OPEN_NEW_BUFFER);
748 function describe_preference_new_window (I) {
749     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
750                         OPEN_NEW_WINDOW);
752 interactive("describe-preference", null,
753             alternates(describe_preference_new_buffer, describe_preference_new_window));