Give automatically created prefix keymaps a name for debugging purposes
[conkeror.git] / modules / help.js
blobbe57bebc45981c86690ff25b17693ab4ac8d63c8
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 help_buffer(window, element) {
14     keywords(arguments);
15     conkeror.buffer.call(this, window, element, forward_keywords(arguments));
18 help_buffer.prototype = {
19     constructor: help_buffer,
21     __proto__: special_buffer.prototype
24 function where_is_command(buffer, command) {
25     var list = find_command_in_keymap(buffer, command);
26     var msg;
27     if (list.length == 0)
28         msg = command + " is not on any key";
29     else
30         msg = command + " is on " + list.join(", ");
31     buffer.window.minibuffer.message(msg);
33 interactive("where-is", null, function (I) {
34     where_is_command(I.buffer,
35                      (yield I.minibuffer.read_command($prompt = "Where is command:")));
36 });
38 function help_document_generator(document, buffer) {
39     dom_generator.call(this, document, XHTML_NS);
40     this.buffer = buffer;
42 help_document_generator.prototype = {
43     __proto__: dom_generator.prototype,
45     key_binding : function(str, parent) {
46         var node = this.element("span", "class", "key-binding");
47         this.text(str, node);
48         if (parent)
49             parent.appendChild(node);
50         return node;
51     },
53     source_code_reference : function(ref, parent) {
54         var f = this.document.createDocumentFragment();
55         var module_name = ref.module_name;
56         //f.appendChild(this.text(module_name != null ? "module " : "file "));
57         var x = this.element("a",
58                              "class", "source-code-reference",
59                              "href", "javascript:");
60         x.addEventListener("click", function (event) {
61             co_call(ref.open_in_editor());
62             event.preventDefault();
63             event.stopPropagation();
64         }, false /* capture */, false /* allow untrusted */);
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 */, false /* allow untrusted */);
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     }
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.constructor_end();
149 describe_bindings_buffer.prototype = {
151     get keymap() {
152         return help_buffer_keymap;
153     },
155     title : "Key bindings",
157     description : "*bindings*",
159     generate : function () {
160         var d = this.document;
161         var list = this.binding_list;
162         delete this.binding_list;
164         var list_by_keymap = {};
165         var keymap_list = [];
166         for each (let x in list) {
167             let name = x.bound_in || "";
168             let km;
169             if (name in list_by_keymap)
170                 km = list_by_keymap[name];
171             else {
172                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
173                 keymap_list.push(km);
174             }
175             let catname = x.category || "";
176             let cat;
177             if (catname in km.list_by_category)
178                 cat = km.list_by_category[catname];
179             else {
180                 cat = km.list_by_category[catname] = [];
181                 cat.name = catname;
182                 if (catname == "")
183                     km.category_list.unshift(cat);
184                 else
185                     km.category_list.push(cat);
186             }
187             cat.push(x);
188         }
190         var g = new help_document_generator(d, this);
191         g.add_help_stylesheet();
193         d.body.setAttribute("class", "help-list");
195         for each (let km in keymap_list) {
196             g.text(km.name, g.element("h1", d.body));
197             for each (let cat in km.category_list) {
198                 if (cat.name != "")
199                     g.text(cat.name, g.element("h2", d.body));
201                 let table = g.element("table", d.body);
202                 for (var i = 0; i < cat.length; ++i) {
203                     let bind = cat[i];
204                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
205                     let seq_td = g.element("td", tr, "class", "key-binding");
206                     g.text(bind.seq, seq_td);
207                     let command_td = g.element("td", tr, "class", "command");
208                     let help_str = null;
209                     if (bind.command != null) {
210                         if (typeof(bind.command) == "function") {
211                             g.text("[function]", command_td);
212                         } else {
213                             g.text(bind.command, command_td);
214                             let cmd = interactive_commands.get(bind.command);
215                             if (cmd != null)
216                                 help_str = cmd.shortdoc;
217                         }
218                     }
219                     else if (bind.fallthrough)
220                         g.text("[pass through]", command_td);
221                     let help_td = g.element("td", tr, "class", "help");
222                     g.text(help_str || "", help_td);
223                 }
224             }
225         }
226     },
228     __proto__: special_buffer.prototype
232 function describe_bindings(buffer, target) {
233     var list = [];
234     for_each_key_binding(buffer, function (binding_stack) {
235             var last = binding_stack[binding_stack.length - 1];
236             if (last.command == null && !last.fallthrough)
237                 return;
238             let bound_in = null;
239         outer:
240             for (let i = binding_stack.length - 1; i >= 0; --i) {
241                 bound_in = binding_stack[i].bound_in;
242                 while (bound_in) {
243                     if (bound_in.name && !bound_in.anonymous)
244                         break outer;
245                     bound_in = bound_in.bound_in;
246                 }
247             }
248             var bind = {seq: format_binding_sequence(binding_stack),
249                         fallthrough: last.fallthrough,
250                         command: last.command,
251                         bound_in: bound_in.name,
252                         category: last.category
253                        };
254             list.push(bind);
255         });
256     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
257                                                 $configuration = buffer.configuration,
258                                                 $binding_list = list),
259                   target);
261 function describe_bindings_new_buffer (I) {
262     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
264 function describe_bindings_new_window (I) {
265     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
267 interactive("describe-bindings", null,
268             alternates(describe_bindings_new_buffer, describe_bindings_new_window));
271 define_keywords("$command_list");
272 function apropos_command_buffer(window, element) {
273     this.constructor_begin();
274     keywords(arguments);
275     special_buffer.call(this, window, element, forward_keywords(arguments));
276     this.command_list = arguments.$command_list;
277     this.constructor_end();
280 apropos_command_buffer.prototype = {
282     get keymap() {
283         return help_buffer_keymap;
284     },
286     title : "Apropos commands",
288     description : "*Apropos*",
290     generate : function () {
291         var d = this.document;
292         var list = this.command_list;
293         delete this.command_list;
295         var g = new help_document_generator(d, this);
296         g.add_help_stylesheet();
298         d.body.setAttribute("class", "help-list");
300         var table = d.createElementNS(XHTML_NS, "table");
301         for (var i = 0; i < list.length; ++i) {
302             var binding = list[i];
303             var tr = d.createElementNS(XHTML_NS, "tr");
304             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
306             var command_td = d.createElementNS(XHTML_NS,"td");
307             g.command_reference(binding.name, command_td);
309             var shortdoc = "";
310             if (binding.cmd.shortdoc != null)
311                 shortdoc = binding.cmd.shortdoc;
312             tr.appendChild(command_td);
314             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
315             shortdoc_td.setAttribute("class", "help");
316             shortdoc_td.textContent = shortdoc;
317             tr.appendChild(shortdoc_td);
319             table.appendChild(tr);
320         }
321         d.body.appendChild(table);
322     },
324     __proto__: special_buffer.prototype
328 /* TODO: support regexps/etc. */
329 function apropos_command(buffer, substring, target) {
330     var list = [];
331     interactive_commands.for_each(function (name, cmd) {
332         if (name.indexOf(substring) != -1) {
333             var binding = {name: name, cmd: cmd};
334             list.push(binding);
335         }
336     });
337     list.sort(function (a,b) {
338                   if (a.name < b.name)
339                       return -1;
340                   if (a.name > b.name)
341                       return 1;
342                   return 0
343               });
344     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
345                                                 $configuration = buffer.configuration,
346                                                 $command_list = list),
347                   target);
350 function apropos_command_new_buffer (I) {
351     apropos_command(I.buffer,
352                     (yield I.minibuffer.read($prompt = "Apropos command:",
353                                              $history = "apropos")),
354                     OPEN_NEW_BUFFER);
356 function apropos_command_new_window (I) {
357     apropos_command(I.buffer,
358                     (yield I.minibuffer.read($prompt = "Apropos command:",
359                                              $history = "apropos")),
360                     OPEN_NEW_WINDOW);
362 interactive("apropos-command", "List commands whose names contain a given substring.",
363             alternates(apropos_command_new_buffer, apropos_command_new_window));
367 define_keywords("$command", "$bindings");
368 function describe_command_buffer(window, element) {
369     this.constructor_begin();
370     keywords(arguments);
371     special_buffer.call(this, window, element, forward_keywords(arguments));
372     this.bindings = arguments.$bindings;
373     this.command = arguments.$command;
374     this.cmd = interactive_commands.get(this.command);
375     this.source_code_reference = this.cmd.source_code_reference;
376     this.constructor_end();
379 describe_command_buffer.prototype = {
381     get keymap() {
382         return help_buffer_keymap;
383     },
385     get title() { return "Command help: " + this.command; },
387     description : "*help*",
389     generate : function () {
390         var d = this.document;
392         var g = new help_document_generator(d, this);
394         g.add_help_stylesheet();
395         d.body.setAttribute("class", "describe-command");
397         var p;
399         p = g.element("p", d.body);
400         g.command_reference(this.command, p);
401         var cmd = interactive_commands.get(this.command);
402         if (cmd.source_code_reference)  {
403             g.text(" is an interactive command in ", p);
404             g.source_code_reference(cmd.source_code_reference, p);
405             g.text(".", p);
406         } else {
407             g.text(" is an interactive command.", p);
408         }
410         if (this.bindings.length > 0) {
411             p = g.element("p", d.body);
412             g.text("It is bound to ", p);
413             for (var i = 0; i < this.bindings.length; ++i) {
414                 if (i != 0)
415                     g.text(", ", p);
416                 g.key_binding(this.bindings[i], p);
417             }
418             g.text(".", p);
419         }
421         if (cmd.doc != null)
422             g.help_text(cmd.doc, d.body);
423     },
425     __proto__: special_buffer.prototype
429 function describe_command(buffer, command, target) {
430     var bindings = find_command_in_keymap(buffer, command);
431     create_buffer(buffer.window,
432                   buffer_creator(describe_command_buffer,
433                                  $configuration = buffer.configuration,
434                                  $command = command,
435                                  $bindings = bindings),
436                   target);
438 function describe_command_new_buffer (I) {
439     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
440                      OPEN_NEW_BUFFER);
442 function describe_command_new_window (I) {
443     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
444                      OPEN_NEW_WINDOW);
446 interactive("describe-command", null,
447             alternates(describe_command_new_buffer, describe_command_new_window));
454 function view_referenced_source_code(buffer) {
455     if (buffer.source_code_reference == null)
456         throw interactive_error("Command not valid in current buffer.");
457     yield buffer.source_code_reference.open_in_editor();
459 interactive("view-referenced-source-code", null, function (I) {yield view_referenced_source_code(I.buffer);});
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.constructor_end();
474 describe_key_buffer.prototype = {
476     get keymap() {
477         return help_buffer_keymap;
478     },
480     get title() { return "Key help: " + this.key_sequence; },
482     description : "*help*",
484     generate : function () {
485         var d = this.document;
487         var g = new help_document_generator(d, this);
489         g.add_help_stylesheet();
490         d.body.setAttribute("class", "describe-key");
492         var p;
494         p = g.element("p", d.body);
495         g.key_binding(this.key_sequence, p);
496         g.text(" is bound to the command ", p);
497         var command = this.bind.command;
498         if (command == null)
499             g.command_name("[pass through]", p);
500         else
501             g.command_reference(command, p);
502         if (this.source_code_reference) {
503             g.text(" in ", p);
504             g.source_code_reference(this.source_code_reference, p);
505         }
506         g.text(".", p);
508         if (command != null) {
509             p = g.element("p", d.body);
510             g.command_reference(command, p);
511             var cmd = interactive_commands.get(command);
512             if (cmd.source_code_reference)  {
513                 g.text(" is an interactive command in ", p);
514                 g.source_code_reference(cmd.source_code_reference, p);
515                 g.text(".", p);
516             } else {
517                 g.text(" is an interactive command.", p);
518             }
520             if (this.bindings.length > 0) {
521                 p = g.element("p", d.body);
522                 g.text("It is bound to ", p);
523                 for (var i = 0; i < this.bindings.length; ++i) {
524                     if (i != 0)
525                         g.text(", ", p);
526                     g.key_binding(this.bindings[i], p);
527                 }
528                 g.text(".", p);
529             }
531             if (cmd.doc != null)
532                 g.help_text(cmd.doc, d.body);
533         }
534     },
536     __proto__: special_buffer.prototype
540 function describe_key(buffer, key_info, target) {
541     var bindings;
542     var seq = key_info[0];
543     var bind = key_info[1];
545     if (bind.command)
546         bindings = find_command_in_keymap(buffer, bind.command);
547     else
548         bindings = [];
550     create_buffer(buffer.window,
551                   buffer_creator(describe_key_buffer,
552                                  $configuration = buffer.configuration,
553                                  $key_sequence = seq.join(" "),
554                                  $other_bindings = bindings,
555                                  $binding = bind),
556                   target);
558 function describe_key_new_buffer (I) {
559     describe_key(I.buffer,
560                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
561                  OPEN_NEW_BUFFER);
563 function describe_key_new_window (I) {
564     describe_key(I.buffer,
565                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
566                  OPEN_NEW_WINDOW);
569 function describe_key_briefly(buffer, key_info) {
570     var bindings;
571     var seq = key_info[0];
572     var bind = key_info[1];
574     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command);
577 interactive("describe-key", null,
578             alternates(describe_key_new_buffer, describe_key_new_window));
579 interactive("describe-key-briefly", null, function (I) {
580     describe_key_briefly(
581         I.buffer,
582         (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
588 define_keywords("$variable");
589 function describe_variable_buffer(window, element) {
590     this.constructor_begin();
591     keywords(arguments);
592     special_buffer.call(this, window, element, forward_keywords(arguments));
593     this.variable = arguments.$variable;
594     this.cmd = user_variables.get(this.variable);
595     this.source_code_reference = this.cmd.source_code_reference;
596     this.constructor_end();
599 function pretty_print_value(value) {
600     if (value === undefined)
601         return "undefined";
602     if (value === null)
603         return "null";
604     if (typeof(value) == "object")
605         return value.toSource();
606     if (typeof(value) == "function")
607         return value.toString();
608     if (typeof(value) == "string") {
609         let s = value.toSource();
610         // toSource returns: (new String("<blah>"))
611         // we want just: "<blah>"
612         return s.substring(12, s.length - 2);
613     }
614     return new String(value);
617 describe_variable_buffer.prototype = {
619     get keymap() {
620         return help_buffer_keymap;
621     },
623     get title() { return "Variable help: " + this.variable; },
625     description : "*help*",
627     generate : function () {
628         var d = this.document;
630         var g = new help_document_generator(d, this);
632         g.add_help_stylesheet();
633         d.body.setAttribute("class", "describe-variable");
635         var p;
637         p = g.element("p", d.body);
638         g.variable_reference(this.variable, p);
639         var uvar = user_variables.get(this.variable);
640         if (uvar.source_code_reference)  {
641             g.text(" is a user variable in ", p);
642             g.source_code_reference(uvar.source_code_reference, p);
643             g.text(".", p);
644         } else {
645             g.text(" is a user variable.", p);
646         }
648         p = g.element("p", d.body);
649         g.text("Its value is: ", p);
650         let value = conkeror[this.variable];
651         {
652             let s = pretty_print_value(value);
653             let pre = g.element("pre", p);
654             g.text(s, pre);
655         }
657         if (uvar.doc != null)
658             g.help_text(uvar.doc, d.body);
660         if (uvar.default_value !== undefined &&
661             (uvar.default_value !== value ||
662              (typeof(uvar.default_value) != "object")))  {
663             p = g.element("p", d.body);
664             g.text("Its default value is: ", p);
665             {
666                 let s = pretty_print_value(uvar.default_value);
667                 let pre = g.element("pre", p);
668                 g.text(s, pre);
669             }
670         }
671     },
673     __proto__: special_buffer.prototype
677 function describe_variable(buffer, variable, target) {
678     create_buffer(buffer.window,
679                   buffer_creator(describe_variable_buffer,
680                                  $configuration = buffer.configuration,
681                                  $variable = variable),
682                   target);
684 function describe_variable_new_buffer (I) {
685     describe_variable(I.buffer, (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
686                       OPEN_NEW_BUFFER);
688 function describe_variable_new_window (I) {
689     describe_variable(I.buffer, (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
690                       OPEN_NEW_WINDOW);
692 interactive("describe-variable", null,
693             alternates(describe_variable_new_buffer, describe_variable_new_window));
697 function describe_preference(buffer, preference, target) {
698     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
699     let url = "http://kb.mozillazine.org/" + key;
700     browser_object_follow(buffer, target, url);
702 function describe_preference_new_buffer (I) {
703     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
704                         OPEN_NEW_BUFFER);
706 function describe_preference_new_window (I) {
707     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
708                         OPEN_NEW_WINDOW);
710 interactive("describe-preference", null,
711             alternates(describe_preference_new_buffer, describe_preference_new_window));