dynamic keymaps
[conkeror.git] / modules / help.js
blobbe15720b651e21ab3c5e2b7dec503e879fb288f7
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         //f.appendChild(this.text(module_name != null ? "module " : "file "));
48         var x = this.element("a",
49                              "class", "source-code-reference",
50                              "href", "javascript:");
51         x.addEventListener("click", function (event) {
52             co_call(ref.open_in_editor());
53             event.preventDefault();
54             event.stopPropagation();
55         }, false /* capture */);
56         x.textContent = (module_name != null ? module_name : ref.file_name);
57         f.appendChild(x);
58         if (parent)
59             parent.appendChild(f);
60         return f;
61     },
63     command_name : function (name, parent) {
64         var node = this.element("span", "class", "command");
65         this.text(name, node);
66         if (parent)
67             parent.appendChild(node);
68         return node;
69     },
71     command_reference : function (name, parent) {
72         var node = this.element("a",
73                                 "class", "command",
74                                 "href", "javascript:");
75         var buffer = this.buffer;
76         node.addEventListener("click", function (event) {
77                                   /* FIXME: don't hardcode browse target */
78                                   describe_command(buffer, name, OPEN_NEW_BUFFER);
79                                   event.preventDefault();
80                                   event.stopPropagation();
81             }, false /* capture */);
82         this.text(name, node);
83         if (parent)
84             parent.appendChild(node);
85         return node;
86     },
88     variable_reference : function (name, parent) {
89         var node = this.element("a", "class", "variable", "href", "#");
90         /* FIXME: make this work */
91         this.text(name, node);
92         if (parent)
93             parent.appendChild(node);
94         return node;
95     },
97     help_text : function (str, parent) {
98         var paras = str.split("\n");
99         var f = this.document.createDocumentFragment();
100         for (var i = 0; i < paras.length; ++i) {
101             var para = paras[i];
102             if (para.length == 0)
103                 continue;
105             var p = this.element("p", f);
107             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
109             var match;
110             var last_index = 0;
111             while ((match = regexp.exec(para)) != null) {
112                 this.text(para.substring(last_index, match.index), p);
113                 var command = match[1];
114                 /* FIXME: check if it is a valid command */
115                 this.command_reference(command, p);
116                 last_index = regexp.lastIndex;
117             }
118             if (last_index < para.length)
119                 this.text(para.substring(last_index), p);
120         }
121         if (parent != null)
122             parent.appendChild(f);
123         return f;
124     },
126     add_help_stylesheet : function () {
127         this.add_stylesheet("chrome://conkeror-gui/content/help.css");
128     }
132 function help_buffer_modality (buffer, element) {
133     buffer.keymaps.push(help_buffer_keymap);
137  * Describe Bindings
138  */
140 define_keywords("$binding_list");
141 function describe_bindings_buffer (window, element) {
142     this.constructor_begin();
143     keywords(arguments);
144     special_buffer.call(this, window, element, forward_keywords(arguments));
145     this.binding_list = arguments.$binding_list;
146     this.modalities.push(help_buffer_modality);
147     this.constructor_end();
150 describe_bindings_buffer.prototype = {
151     title : "Key bindings",
153     description : "*bindings*",
155     generate : function () {
156         var d = this.document;
157         var list = this.binding_list;
158         delete this.binding_list;
160         var list_by_keymap = {};
161         var keymap_list = [];
162         for each (let x in list) {
163             let name = x.bound_in || "";
164             let km;
165             if (name in list_by_keymap)
166                 km = list_by_keymap[name];
167             else {
168                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
169                 keymap_list.push(km);
170             }
171             let catname = x.category || "";
172             let cat;
173             if (catname in km.list_by_category)
174                 cat = km.list_by_category[catname];
175             else {
176                 cat = km.list_by_category[catname] = [];
177                 cat.name = catname;
178                 if (catname == "")
179                     km.category_list.unshift(cat);
180                 else
181                     km.category_list.push(cat);
182             }
183             cat.push(x);
184         }
186         var g = new help_document_generator(d, this);
187         g.add_help_stylesheet();
189         d.body.setAttribute("class", "help-list");
191         for each (let km in keymap_list) {
192             g.text(km.name, g.element("h1", d.body));
193             for each (let cat in km.category_list) {
194                 if (cat.name != "")
195                     g.text(cat.name, g.element("h2", d.body));
197                 let table = g.element("table", d.body);
198                 for (var i = 0; i < cat.length; ++i) {
199                     let bind = cat[i];
200                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
201                     let seq_td = g.element("td", tr, "class", "key-binding");
202                     g.text(bind.seq, seq_td);
203                     let command_td = g.element("td", tr, "class", "command");
204                     let help_str = null;
205                     if (bind.command != null) {
206                         if (typeof(bind.command) == "function") {
207                             g.text("[function]", command_td);
208                         } else {
209                             g.text(bind.command, command_td);
210                             let cmd = interactive_commands.get(bind.command);
211                             if (cmd != null)
212                                 help_str = cmd.shortdoc;
213                         }
214                     } else if (bind.fallthrough)
215                         g.text("[pass through]", command_td);
216                     let help_td = g.element("td", tr, "class", "help");
217                     g.text(help_str || "", help_td);
218                 }
219             }
220         }
221     },
223     __proto__: special_buffer.prototype
227 function describe_bindings (buffer, target) {
228     var list = [];
229     var keymaps = get_current_keymaps(buffer.window);
230     for_each_key_binding(keymaps, function (binding_stack) {
231             var last = binding_stack[binding_stack.length - 1];
232             if (last.command == null && !last.fallthrough)
233                 return;
234             let bound_in = null;
235         outer:
236             for (let i = binding_stack.length - 1; i >= 0; --i) {
237                 bound_in = binding_stack[i].bound_in;
238                 while (bound_in) {
239                     if (bound_in.name && !bound_in.anonymous)
240                         break outer;
241                     bound_in = bound_in.bound_in;
242                 }
243             }
244             var bind = {seq: format_binding_sequence(binding_stack),
245                         fallthrough: last.fallthrough,
246                         command: last.command,
247                         bound_in: bound_in.name,
248                         category: last.category
249                        };
250             list.push(bind);
251         });
252     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
253                                                 $opener = buffer,
254                                                 $binding_list = list),
255                   target);
257 function describe_bindings_new_buffer (I) {
258     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
260 function describe_bindings_new_window (I) {
261     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
263 interactive("describe-bindings", null,
264             alternates(describe_bindings_new_buffer, describe_bindings_new_window));
269  * Apropos Command
270  */
272 define_keywords("$command_list");
273 function apropos_command_buffer (window, element) {
274     this.constructor_begin();
275     keywords(arguments);
276     special_buffer.call(this, window, element, forward_keywords(arguments));
277     this.command_list = arguments.$command_list;
278     this.modalities.push(help_buffer_modality);
279     this.constructor_end();
282 apropos_command_buffer.prototype = {
283     title : "Apropos commands",
285     description : "*Apropos*",
287     generate : function () {
288         var d = this.document;
289         var list = this.command_list;
290         delete this.command_list;
292         var g = new help_document_generator(d, this);
293         g.add_help_stylesheet();
295         d.body.setAttribute("class", "help-list");
297         var table = d.createElementNS(XHTML_NS, "table");
298         for (var i = 0; i < list.length; ++i) {
299             var binding = list[i];
300             var tr = d.createElementNS(XHTML_NS, "tr");
301             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
303             var command_td = d.createElementNS(XHTML_NS,"td");
304             g.command_reference(binding.name, command_td);
306             var shortdoc = "";
307             if (binding.cmd.shortdoc != null)
308                 shortdoc = binding.cmd.shortdoc;
309             tr.appendChild(command_td);
311             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
312             shortdoc_td.setAttribute("class", "help");
313             shortdoc_td.textContent = shortdoc;
314             tr.appendChild(shortdoc_td);
316             table.appendChild(tr);
317         }
318         d.body.appendChild(table);
319     },
321     __proto__: special_buffer.prototype
325 /* TODO: support regexps/etc. */
326 function apropos_command (buffer, substring, target) {
327     var list = [];
328     interactive_commands.for_each(function (name, cmd) {
329         if (name.indexOf(substring) != -1) {
330             var binding = {name: name, cmd: cmd};
331             list.push(binding);
332         }
333     });
334     list.sort(function (a,b) {
335                   if (a.name < b.name)
336                       return -1;
337                   if (a.name > b.name)
338                       return 1;
339                   return 0
340               });
341     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
342                                                 $opener = buffer,
343                                                 $command_list = list),
344                   target);
347 function apropos_command_new_buffer (I) {
348     apropos_command(I.buffer,
349                     (yield I.minibuffer.read($prompt = "Apropos command:",
350                                              $history = "apropos")),
351                     OPEN_NEW_BUFFER);
353 function apropos_command_new_window (I) {
354     apropos_command(I.buffer,
355                     (yield I.minibuffer.read($prompt = "Apropos command:",
356                                              $history = "apropos")),
357                     OPEN_NEW_WINDOW);
359 interactive("apropos-command", "List commands whose names contain a given substring.",
360             alternates(apropos_command_new_buffer, apropos_command_new_window));
365  * Describe Command
366  */
368 define_keywords("$command", "$bindings");
369 function describe_command_buffer (window, element) {
370     this.constructor_begin();
371     keywords(arguments);
372     special_buffer.call(this, window, element, forward_keywords(arguments));
373     this.bindings = arguments.$bindings;
374     this.command = arguments.$command;
375     this.cmd = interactive_commands.get(this.command);
376     this.source_code_reference = this.cmd.source_code_reference;
377     this.modalities.push(help_buffer_modality);
378     this.constructor_end();
381 describe_command_buffer.prototype = {
382     get title() { return "Command help: " + this.command; },
384     description : "*help*",
386     generate : function () {
387         var d = this.document;
389         var g = new help_document_generator(d, this);
391         g.add_help_stylesheet();
392         d.body.setAttribute("class", "describe-command");
394         var p;
396         p = g.element("p", d.body);
397         g.command_reference(this.command, p);
398         var cmd = interactive_commands.get(this.command);
399         if (cmd.source_code_reference)  {
400             g.text(" is an interactive command in ", p);
401             g.source_code_reference(cmd.source_code_reference, p);
402             g.text(".", p);
403         } else {
404             g.text(" is an interactive command.", p);
405         }
407         if (this.bindings.length > 0) {
408             p = g.element("p", d.body);
409             g.text("It is bound to ", p);
410             for (var i = 0; i < this.bindings.length; ++i) {
411                 if (i != 0)
412                     g.text(", ", p);
413                 g.key_binding(this.bindings[i], p);
414             }
415             g.text(".", p);
416         }
418         if (cmd.doc != null)
419             g.help_text(cmd.doc, d.body);
420     },
422     __proto__: special_buffer.prototype
426 function describe_command (buffer, command, target) {
427     var keymaps = get_current_keymaps(buffer.window);
428     var bindings = keymap_lookup_command(keymaps, command);
429     create_buffer(buffer.window,
430                   buffer_creator(describe_command_buffer,
431                                  $opener = buffer,
432                                  $command = command,
433                                  $bindings = bindings),
434                   target);
436 function describe_command_new_buffer (I) {
437     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
438                      OPEN_NEW_BUFFER);
440 function describe_command_new_window (I) {
441     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
442                      OPEN_NEW_WINDOW);
444 interactive("describe-command", null,
445             alternates(describe_command_new_buffer, describe_command_new_window));
449 function view_referenced_source_code (buffer) {
450     if (buffer.source_code_reference == null)
451         throw interactive_error("Command not valid in current buffer.");
452     yield buffer.source_code_reference.open_in_editor();
454 interactive("view-referenced-source-code", null,
455             function (I) {yield view_referenced_source_code(I.buffer);});
459  * Describe Key
460  */
462 define_keywords("$binding", "$other_bindings", "$key_sequence");
463 function describe_key_buffer (window, element) {
464     this.constructor_begin();
465     keywords(arguments);
466     special_buffer.call(this, window, element, forward_keywords(arguments));
467     this.key_sequence = arguments.$key_sequence;
468     this.bindings = arguments.$other_bindings;
469     this.bind = arguments.$binding;
470     this.source_code_reference = this.bind.source_code_reference;
471     this.modalities.push(help_buffer_modality);
472     this.constructor_end();
475 describe_key_buffer.prototype = {
476     get title() { return "Key help: " + this.key_sequence; },
478     description : "*help*",
480     generate : function () {
481         var d = this.document;
483         var g = new help_document_generator(d, this);
485         g.add_help_stylesheet();
486         d.body.setAttribute("class", "describe-key");
488         var p;
490         p = g.element("p", d.body);
491         g.key_binding(this.key_sequence, p);
492         g.text(" is bound to the command ", p);
493         var command = this.bind.command;
494         if (command == null)
495             g.command_name("[pass through]", p);
496         else
497             g.command_reference(command, p);
498         if (this.bind.browser_object != null) {
499             g.text(" with the browser object, ", p);
500             if (this.bind.browser_object instanceof Function) {
501                 g.text("<anonymous browser-object function>", p);
502             } else if (this.bind.browser_object instanceof browser_object_class) {
503                 g.text(this.bind.browser_object.name, p);
504             } else if (typeof(this.bind.browser_object) == "string") {
505                 g.text('"'+this.bind.browser_object+'"', p);
506             } else {
507                 g.text(this.bind.browser_object, p);
508             }
509         }
510         if (this.source_code_reference) {
511             g.text(" in ", p);
512             g.source_code_reference(this.source_code_reference, p);
513         }
514         g.text(".", p);
516         if (command != null) {
517             p = g.element("p", d.body);
518             g.command_reference(command, p);
519             var cmd = interactive_commands.get(command);
520             if (cmd.source_code_reference)  {
521                 g.text(" is an interactive command in ", p);
522                 g.source_code_reference(cmd.source_code_reference, p);
523                 g.text(".", p);
524             } else {
525                 g.text(" is an interactive command.", p);
526             }
528             if (this.bindings.length > 0) {
529                 p = g.element("p", d.body);
530                 g.text("It is bound to ", p);
531                 for (var i = 0; i < this.bindings.length; ++i) {
532                     if (i != 0)
533                         g.text(", ", p);
534                     g.key_binding(this.bindings[i], p);
535                 }
536                 g.text(".", p);
537             }
539             if (cmd.doc != null)
540                 g.help_text(cmd.doc, d.body);
541         }
542     },
544     __proto__: special_buffer.prototype
548 function describe_key (buffer, key_info, target) {
549     var bindings;
550     var seq = key_info[0];
551     var bind = key_info[1];
552     var keymaps = get_current_keymaps(buffer.window);
553     if (bind.command)
554         bindings = keymap_lookup_command(keymaps, bind.command);
555     else
556         bindings = [];
557     create_buffer(buffer.window,
558                   buffer_creator(describe_key_buffer,
559                                  $opener = buffer,
560                                  $key_sequence = seq.join(" "),
561                                  $other_bindings = bindings,
562                                  $binding = bind),
563                   target);
565 function describe_key_new_buffer (I) {
566     describe_key(I.buffer,
567                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
568                  OPEN_NEW_BUFFER);
570 function describe_key_new_window (I) {
571     describe_key(I.buffer,
572                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
573                  OPEN_NEW_WINDOW);
576 function describe_key_briefly (buffer, key_info) {
577     var bindings;
578     var seq = key_info[0];
579     var bind = key_info[1];
580     var browser_object = "";
581     if (bind.browser_object != null) {
582         browser_object += " on the browser object, ";
583         if (bind.browser_object instanceof Function) {
584             browser_object += "<anonymous browser-object function>";
585         } else if (bind.browser_object instanceof browser_object_class) {
586             browser_object += bind.browser_object.name;
587         } else if (typeof(bind.browser_object) == "string") {
588             browser_object += '"'+bind.browser_object+'"';
589         } else {
590             browser_object += bind.browser_object;
591         }
592     }
593     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command + browser_object);
596 interactive("describe-key", null,
597             alternates(describe_key_new_buffer, describe_key_new_window));
599 interactive("describe-key-briefly", null,
600     function (I) {
601         describe_key_briefly(
602             I.buffer,
603             (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
604     });
609  * Describe Variable
610  */
612 define_keywords("$variable");
613 function describe_variable_buffer (window, element) {
614     this.constructor_begin();
615     keywords(arguments);
616     special_buffer.call(this, window, element, forward_keywords(arguments));
617     this.variable = arguments.$variable;
618     this.cmd = user_variables[this.variable];
619     this.source_code_reference = this.cmd.source_code_reference;
620     this.modalities.push(help_buffer_modality);
621     this.constructor_end();
624 function pretty_print_value (value) {
625     if (value === undefined)
626         return "undefined";
627     if (value === null)
628         return "null";
629     if (typeof(value) == "object")
630         return value.toSource();
631     if (typeof(value) == "function")
632         return value.toString();
633     if (typeof(value) == "string") {
634         let s = value.toSource();
635         // toSource returns: (new String("<blah>"))
636         // we want just: "<blah>"
637         return s.substring(12, s.length - 2);
638     }
639     return new String(value);
642 describe_variable_buffer.prototype = {
643     get title() { return "Variable help: " + this.variable; },
645     description : "*help*",
647     generate : function () {
648         var d = this.document;
650         var g = new help_document_generator(d, this);
652         g.add_help_stylesheet();
653         d.body.setAttribute("class", "describe-variable");
655         var p;
657         p = g.element("p", d.body);
658         g.variable_reference(this.variable, p);
659         var uvar = user_variables[this.variable];
660         if (uvar.source_code_reference)  {
661             g.text(" is a user variable in ", p);
662             g.source_code_reference(uvar.source_code_reference, p);
663             g.text(".", p);
664         } else {
665             g.text(" is a user variable.", p);
666         }
668         p = g.element("p", d.body);
669         g.text("Its value is: ", p);
670         let value = conkeror[this.variable];
671         {
672             let s = pretty_print_value(value);
673             let pre = g.element("pre", p);
674             g.text(s, pre);
675         }
677         if (uvar.doc != null)
678             g.help_text(uvar.doc, d.body);
680         if (uvar.default_value !== undefined &&
681             (uvar.default_value !== value ||
682              (typeof(uvar.default_value) != "object")))  {
683             p = g.element("p", d.body);
684             g.text("Its default value is: ", p);
685             {
686                 let s = pretty_print_value(uvar.default_value);
687                 let pre = g.element("pre", p);
688                 g.text(s, pre);
689             }
690         }
691     },
693     __proto__: special_buffer.prototype
697 function describe_variable (buffer, variable, target) {
698     create_buffer(buffer.window,
699                   buffer_creator(describe_variable_buffer,
700                                  $opener = buffer,
701                                  $variable = variable),
702                   target);
704 function describe_variable_new_buffer (I) {
705     describe_variable(I.buffer,
706                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
707                       OPEN_NEW_BUFFER);
709 function describe_variable_new_window (I) {
710     describe_variable(I.buffer,
711                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
712                       OPEN_NEW_WINDOW);
714 interactive("describe-variable", null,
715             alternates(describe_variable_new_buffer, describe_variable_new_window));
720  * Describe Preference
721  */
723 function describe_preference (buffer, preference, target) {
724     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
725     let url = "http://kb.mozillazine.org/" + key;
726     browser_object_follow(buffer, target, url);
728 function describe_preference_new_buffer (I) {
729     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
730                         OPEN_NEW_BUFFER);
732 function describe_preference_new_window (I) {
733     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
734                         OPEN_NEW_WINDOW);
736 interactive("describe-preference", null,
737             alternates(describe_preference_new_buffer, describe_preference_new_window));