minibuffer.pop_all: js efficiency change
[conkeror.git] / modules / help.js
blobd7ac20fa9ed01e44678f8984872c48286380c44a
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     __proto__: dom_generator.prototype,
38     key_binding: function (str, parent) {
39         var node = this.element("span", "class", "key-binding");
40         this.text(str, node);
41         if (parent)
42             parent.appendChild(node);
43         return node;
44     },
46     source_code_reference: function (ref, parent) {
47         var f = this.document.createDocumentFragment();
48         var module_name = ref.module_name;
49         var buffer = this.buffer;
50         //f.appendChild(this.text(module_name != null ? "module " : "file "));
51         var x = this.element("a",
52                              "class", "source-code-reference",
53                              "href", "javascript:");
54         x.addEventListener("click", function (event) {
55             co_call(function () {
56                 try {
57                     yield ref.open_in_editor();
58                 } catch (e) {
59                     handle_interactive_error(buffer.window, e);
60                 }}());
61             event.preventDefault();
62             event.stopPropagation();
63         }, false /* capture */);
64         x.textContent = (module_name != null ? module_name : ref.file_name);
65         f.appendChild(x);
66         if (parent)
67             parent.appendChild(f);
68         return f;
69     },
71     command_name: function (name, parent) {
72         var node = this.element("span", "class", "command");
73         this.text(name, node);
74         if (parent)
75             parent.appendChild(node);
76         return node;
77     },
79     command_reference: function (name, parent) {
80         var node = this.element("a",
81                                 "class", "command",
82                                 "href", "javascript:");
83         var buffer = this.buffer;
84         node.addEventListener("click", function (event) {
85                                   /* FIXME: don't hardcode browse target */
86                                   describe_command(buffer, name, OPEN_NEW_BUFFER);
87                                   event.preventDefault();
88                                   event.stopPropagation();
89             }, false /* capture */);
90         this.text(name, node);
91         if (parent)
92             parent.appendChild(node);
93         return node;
94     },
96     variable_reference: function (name, parent) {
97         var node = this.element("a", "class", "variable", "href", "#");
98         /* FIXME: make this work */
99         this.text(name, node);
100         if (parent)
101             parent.appendChild(node);
102         return node;
103     },
105     help_text: function (str, parent) {
106         var paras = str.split("\n");
107         var f = this.document.createDocumentFragment();
108         for (var i = 0; i < paras.length; ++i) {
109             var para = paras[i];
110             if (para.length == 0)
111                 continue;
113             var p = this.element("p", f);
115             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
117             var match;
118             var last_index = 0;
119             while ((match = regexp.exec(para)) != null) {
120                 this.text(para.substring(last_index, match.index), p);
121                 var command = match[1];
122                 /* FIXME: check if it is a valid command */
123                 this.command_reference(command, p);
124                 last_index = regexp.lastIndex;
125             }
126             if (last_index < para.length)
127                 this.text(para.substring(last_index), p);
128         }
129         if (parent != null)
130             parent.appendChild(f);
131         return f;
132     },
134     add_help_stylesheet: function () {
135         this.add_stylesheet("chrome://conkeror-gui/content/help.css");
136     }
140 function help_buffer_modality (buffer, element) {
141     buffer.keymaps.push(help_buffer_keymap);
145  * Describe Bindings
146  */
148 define_keywords("$binding_list");
149 function describe_bindings_buffer (window, element) {
150     this.constructor_begin();
151     keywords(arguments);
152     special_buffer.call(this, window, element, forward_keywords(arguments));
153     this.binding_list = arguments.$binding_list;
154     this.modalities.push(help_buffer_modality);
155     this.constructor_end();
158 describe_bindings_buffer.prototype = {
159     title: "Key bindings",
161     description: "*bindings*",
163     generate: function () {
164         var d = this.document;
165         var list = this.binding_list;
166         delete this.binding_list;
168         var list_by_keymap = {};
169         var keymap_list = [];
170         for each (let x in list) {
171             let name = x.bound_in || "";
172             let km;
173             if (name in list_by_keymap)
174                 km = list_by_keymap[name];
175             else {
176                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
177                 keymap_list.push(km);
178             }
179             let catname = x.category || "";
180             let cat;
181             if (catname in km.list_by_category)
182                 cat = km.list_by_category[catname];
183             else {
184                 cat = km.list_by_category[catname] = [];
185                 cat.name = catname;
186                 if (catname == "")
187                     km.category_list.unshift(cat);
188                 else
189                     km.category_list.push(cat);
190             }
191             cat.push(x);
192         }
194         var g = new help_document_generator(d, this);
195         g.add_help_stylesheet();
197         d.body.setAttribute("class", "help-list");
199         for each (let km in keymap_list) {
200             g.text(km.name, g.element("h1", d.body));
201             for each (let cat in km.category_list) {
202                 if (cat.name != "")
203                     g.text(cat.name, g.element("h2", d.body));
205                 let table = g.element("table", d.body);
206                 for (var i = 0; i < cat.length; ++i) {
207                     let bind = cat[i];
208                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
209                     let seq_td = g.element("td", tr, "class", "key-binding");
210                     g.text(bind.seq, seq_td);
211                     let command_td = g.element("td", tr, "class", "command");
212                     let help_str = null;
213                     if (bind.command != null) {
214                         if (typeof(bind.command) == "function") {
215                             g.text("[function]", command_td);
216                         } else {
217                             let cmd = interactive_commands.get(bind.command);
218                             if (cmd != null) {
219                                 g.command_reference(cmd.name, command_td);
220                                 help_str = cmd.shortdoc;
221                             } else {
222                                 g.text(bind.command, command_td);
223                             }
224                         }
225                     } else if (bind.fallthrough)
226                         g.text("[pass through]", command_td);
227                     let help_td = g.element("td", tr, "class", "help");
228                     g.text(help_str || "", help_td);
229                 }
230             }
231         }
232     },
234     __proto__: special_buffer.prototype
238 function describe_bindings (buffer, target, keymaps, prefix) {
239     var list = [];
240     if (! keymaps)
241         keymaps = get_current_keymaps(buffer.window);
242     if (prefix)
243         prefix = format_binding_sequence(
244             prefix.map(function (x) { return {key:x}; }))+" ";
245     else
246         prefix = "";
247     for_each_key_binding(keymaps, function (binding_stack) {
248             var last = binding_stack[binding_stack.length - 1];
249             if (last.command == null && !last.fallthrough)
250                 return;
251             let bound_in = null;
252         outer:
253             for (let i = binding_stack.length - 1; i >= 0; --i) {
254                 bound_in = binding_stack[i].bound_in;
255                 while (bound_in) {
256                     if (bound_in.name && !bound_in.anonymous)
257                         break outer;
258                     bound_in = bound_in.bound_in;
259                 }
260             }
261             var bind = {seq: prefix+format_binding_sequence(binding_stack),
262                         fallthrough: last.fallthrough,
263                         command: last.command,
264                         bound_in: bound_in.name,
265                         category: last.category
266                        };
267             list.push(bind);
268         });
269     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
270                                                 $opener = buffer,
271                                                 $binding_list = list),
272                   target);
274 function describe_bindings_new_buffer (I) {
275     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
277 function describe_bindings_new_window (I) {
278     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
280 interactive("describe-bindings",
281     "Show a help buffer describing the bindings in the context keymaps, "+
282     "meaning the top-level keymaps according to the focus context in the "+
283     "current buffer.",
284     alternates(describe_bindings_new_buffer,
285                describe_bindings_new_window));
287 function describe_active_bindings_new_buffer (I) {
288     describe_bindings(I.buffer, OPEN_NEW_BUFFER,
289                       I.keymaps || get_current_keymaps(I.buffer.window),
290                       I.key_sequence.slice(0, -1));
292 function describe_active_bindings_new_window (I) {
293     describe_bindings(I.buffer, OPEN_NEW_WINDOW,
294                       I.keymaps || get_current_keymaps(I.buffer.window),
295                       I.key_sequence.slice(0, -1));
297 interactive("describe-active-bindings",
298     "Show a help buffer describing the bindings in the active keymaps, "+
299     "meaning the keymaps in the middle of an ongoing key sequence.  This "+
300     "command is intended to be called via `sequence_help_keymap'.  For "+
301     "that reason, `describe-active-bindings' does not consume and prefix "+
302     "commands like `universal-argument', as doing so would lead to "+
303     "ambiguities with respect to the intent of the user.",
304     describe_active_bindings_new_buffer);
308  * Apropos Command
309  */
311 define_keywords("$command_list");
312 function apropos_command_buffer (window, element) {
313     this.constructor_begin();
314     keywords(arguments);
315     special_buffer.call(this, window, element, forward_keywords(arguments));
316     this.command_list = arguments.$command_list;
317     this.modalities.push(help_buffer_modality);
318     this.constructor_end();
321 apropos_command_buffer.prototype = {
322     title: "Apropos commands",
324     description: "*Apropos*",
326     generate: function () {
327         var d = this.document;
328         var list = this.command_list;
329         delete this.command_list;
331         var g = new help_document_generator(d, this);
332         g.add_help_stylesheet();
334         d.body.setAttribute("class", "help-list");
336         var table = d.createElementNS(XHTML_NS, "table");
337         for (var i = 0; i < list.length; ++i) {
338             var binding = list[i];
339             var tr = d.createElementNS(XHTML_NS, "tr");
340             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
342             var command_td = d.createElementNS(XHTML_NS,"td");
343             g.command_reference(binding.name, command_td);
345             var shortdoc = "";
346             if (binding.cmd.shortdoc != null)
347                 shortdoc = binding.cmd.shortdoc;
348             tr.appendChild(command_td);
350             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
351             shortdoc_td.setAttribute("class", "help");
352             shortdoc_td.textContent = shortdoc;
353             tr.appendChild(shortdoc_td);
355             table.appendChild(tr);
356         }
357         d.body.appendChild(table);
358     },
360     __proto__: special_buffer.prototype
364 /* TODO: support regexps/etc. */
365 function apropos_command (buffer, substring, target) {
366     var list = [];
367     interactive_commands.for_each(function (name, cmd) {
368         if (name.indexOf(substring) != -1) {
369             var binding = {name: name, cmd: cmd};
370             list.push(binding);
371         }
372     });
373     list.sort(function (a,b) {
374                   if (a.name < b.name)
375                       return -1;
376                   if (a.name > b.name)
377                       return 1;
378                   return 0
379               });
380     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
381                                                 $opener = buffer,
382                                                 $command_list = list),
383                   target);
386 function apropos_command_new_buffer (I) {
387     apropos_command(I.buffer,
388                     (yield I.minibuffer.read($prompt = "Apropos command:",
389                                              $history = "apropos")),
390                     OPEN_NEW_BUFFER);
392 function apropos_command_new_window (I) {
393     apropos_command(I.buffer,
394                     (yield I.minibuffer.read($prompt = "Apropos command:",
395                                              $history = "apropos")),
396                     OPEN_NEW_WINDOW);
398 interactive("apropos-command", "List commands whose names contain a given substring.",
399             alternates(apropos_command_new_buffer, apropos_command_new_window));
404  * Describe Command
405  */
407 define_keywords("$command", "$bindings");
408 function describe_command_buffer (window, element) {
409     this.constructor_begin();
410     keywords(arguments);
411     special_buffer.call(this, window, element, forward_keywords(arguments));
412     this.bindings = arguments.$bindings;
413     this.command = arguments.$command;
414     this.cmd = interactive_commands.get(this.command);
415     this.source_code_reference = this.cmd.source_code_reference;
416     this.modalities.push(help_buffer_modality);
417     this.constructor_end();
420 describe_command_buffer.prototype = {
421     get title () { return "Command help: " + this.command; },
423     description: "*help*",
425     generate: function () {
426         var d = this.document;
428         var g = new help_document_generator(d, this);
430         g.add_help_stylesheet();
431         d.body.setAttribute("class", "describe-command");
433         var p;
435         p = g.element("p", d.body);
436         g.command_reference(this.command, p);
437         var cmd = interactive_commands.get(this.command);
438         if (cmd.source_code_reference)  {
439             g.text(" is an interactive command in ", p);
440             g.source_code_reference(cmd.source_code_reference, p);
441             g.text(".", p);
442         } else {
443             g.text(" is an interactive command.", p);
444         }
446         if (this.bindings.length > 0) {
447             p = g.element("p", d.body);
448             g.text("It is bound to ", p);
449             for (var i = 0; i < this.bindings.length; ++i) {
450                 if (i != 0)
451                     g.text(", ", p);
452                 g.key_binding(this.bindings[i], p);
453             }
454             g.text(".", p);
455         }
457         if (cmd.doc != null)
458             g.help_text(cmd.doc, d.body);
459     },
461     __proto__: special_buffer.prototype
465 function describe_command (buffer, command, target) {
466     var keymaps = get_current_keymaps(buffer.window);
467     var bindings = keymap_lookup_command(keymaps, command);
468     create_buffer(buffer.window,
469                   buffer_creator(describe_command_buffer,
470                                  $opener = buffer,
471                                  $command = command,
472                                  $bindings = bindings),
473                   target);
475 function describe_command_new_buffer (I) {
476     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
477                      OPEN_NEW_BUFFER);
479 function describe_command_new_window (I) {
480     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
481                      OPEN_NEW_WINDOW);
483 interactive("describe-command", null,
484             alternates(describe_command_new_buffer, describe_command_new_window));
488 function view_referenced_source_code (buffer) {
489     if (buffer.source_code_reference == null)
490         throw interactive_error("Command not valid in current buffer.");
491     yield buffer.source_code_reference.open_in_editor();
493 interactive("view-referenced-source-code", null,
494             function (I) {yield view_referenced_source_code(I.buffer);});
498  * Describe Key
499  */
501 define_keywords("$binding", "$other_bindings", "$key_sequence");
502 function describe_key_buffer (window, element) {
503     this.constructor_begin();
504     keywords(arguments);
505     special_buffer.call(this, window, element, forward_keywords(arguments));
506     this.key_sequence = arguments.$key_sequence;
507     this.bindings = arguments.$other_bindings;
508     this.bind = arguments.$binding;
509     this.source_code_reference = this.bind.source_code_reference;
510     this.modalities.push(help_buffer_modality);
511     this.constructor_end();
514 describe_key_buffer.prototype = {
515     get title () { return "Key help: " + this.key_sequence; },
517     description: "*help*",
519     generate: function () {
520         var d = this.document;
522         var g = new help_document_generator(d, this);
524         g.add_help_stylesheet();
525         d.body.setAttribute("class", "describe-key");
527         var p;
529         p = g.element("p", d.body);
530         g.key_binding(this.key_sequence, p);
531         g.text(" is bound to the command ", p);
532         var command = this.bind.command;
533         if (command == null)
534             g.command_name("[pass through]", p);
535         else
536             g.command_reference(command, p);
537         if (this.bind.browser_object != null) {
538             g.text(" with the browser object, ", p);
539             if (this.bind.browser_object instanceof Function) {
540                 g.text("<anonymous browser-object function>", p);
541             } else if (this.bind.browser_object instanceof browser_object_class) {
542                 g.text(this.bind.browser_object.name, p);
543             } else if (typeof(this.bind.browser_object) == "string") {
544                 g.text('"'+this.bind.browser_object+'"', p);
545             } else {
546                 g.text(this.bind.browser_object, p);
547             }
548         }
549         if (this.source_code_reference) {
550             g.text(" in ", p);
551             g.source_code_reference(this.source_code_reference, p);
552         }
553         g.text(".", p);
555         if (command != null) {
556             p = g.element("p", d.body);
557             g.command_reference(command, p);
558             var cmd = interactive_commands.get(command);
559             if (cmd.source_code_reference)  {
560                 g.text(" is an interactive command in ", p);
561                 g.source_code_reference(cmd.source_code_reference, p);
562                 g.text(".", p);
563             } else {
564                 g.text(" is an interactive command.", p);
565             }
567             if (this.bindings.length > 0) {
568                 p = g.element("p", d.body);
569                 g.text("It is bound to ", p);
570                 for (var i = 0; i < this.bindings.length; ++i) {
571                     if (i != 0)
572                         g.text(", ", p);
573                     g.key_binding(this.bindings[i], p);
574                 }
575                 g.text(".", p);
576             }
578             if (cmd.doc != null)
579                 g.help_text(cmd.doc, d.body);
580         }
581     },
583     __proto__: special_buffer.prototype
587 function describe_key (buffer, key_info, target) {
588     var bindings;
589     var seq = key_info[0];
590     var bind = key_info[1];
591     var keymaps = get_current_keymaps(buffer.window);
592     if (bind.command)
593         bindings = keymap_lookup_command(keymaps, bind.command);
594     else
595         bindings = [];
596     create_buffer(buffer.window,
597                   buffer_creator(describe_key_buffer,
598                                  $opener = buffer,
599                                  $key_sequence = seq.join(" "),
600                                  $other_bindings = bindings,
601                                  $binding = bind),
602                   target);
604 function describe_key_new_buffer (I) {
605     describe_key(I.buffer,
606                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
607                  OPEN_NEW_BUFFER);
609 function describe_key_new_window (I) {
610     describe_key(I.buffer,
611                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
612                  OPEN_NEW_WINDOW);
615 function describe_key_briefly (buffer, key_info) {
616     var bindings;
617     var seq = key_info[0];
618     var bind = key_info[1];
619     var browser_object = "";
620     if (bind.browser_object != null) {
621         browser_object += " on the browser object, ";
622         if (bind.browser_object instanceof Function) {
623             browser_object += "<anonymous browser-object function>";
624         } else if (bind.browser_object instanceof browser_object_class) {
625             browser_object += bind.browser_object.name;
626         } else if (typeof(bind.browser_object) == "string") {
627             browser_object += '"'+bind.browser_object+'"';
628         } else {
629             browser_object += bind.browser_object;
630         }
631     }
632     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command + browser_object);
635 interactive("describe-key", null,
636             alternates(describe_key_new_buffer, describe_key_new_window));
638 interactive("describe-key-briefly", null,
639     function (I) {
640         describe_key_briefly(
641             I.buffer,
642             (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
643     });
648  * Describe Variable
649  */
651 define_keywords("$variable");
652 function describe_variable_buffer (window, element) {
653     this.constructor_begin();
654     keywords(arguments);
655     special_buffer.call(this, window, element, forward_keywords(arguments));
656     this.variable = arguments.$variable;
657     this.cmd = user_variables[this.variable];
658     this.source_code_reference = this.cmd.source_code_reference;
659     this.modalities.push(help_buffer_modality);
660     this.constructor_end();
663 describe_variable_buffer.prototype = {
664     get title () { return "Variable help: " + this.variable; },
666     description: "*help*",
668     generate: function () {
669         var d = this.document;
671         var g = new help_document_generator(d, this);
673         g.add_help_stylesheet();
674         d.body.setAttribute("class", "describe-variable");
676         var p;
678         p = g.element("p", d.body);
679         g.variable_reference(this.variable, p);
680         var uvar = user_variables[this.variable];
681         if (uvar.source_code_reference)  {
682             g.text(" is a user variable in ", p);
683             g.source_code_reference(uvar.source_code_reference, p);
684             g.text(".", p);
685         } else {
686             g.text(" is a user variable.", p);
687         }
689         p = g.element("p", d.body);
690         g.text("Its value is: ", p);
691         let value = conkeror[this.variable];
692         {
693             let s = pretty_print_value(value);
694             let pre = g.element("pre", p);
695             g.text(s, pre);
696         }
698         if (uvar.doc != null)
699             g.help_text(uvar.doc, d.body);
701         if (uvar.default_value !== undefined &&
702             (uvar.default_value !== value ||
703              (typeof(uvar.default_value) != "object")))  {
704             p = g.element("p", d.body);
705             g.text("Its default value is: ", p);
706             {
707                 let s = pretty_print_value(uvar.default_value);
708                 let pre = g.element("pre", p);
709                 g.text(s, pre);
710             }
711         }
712     },
714     __proto__: special_buffer.prototype
718 function describe_variable (buffer, variable, target) {
719     create_buffer(buffer.window,
720                   buffer_creator(describe_variable_buffer,
721                                  $opener = buffer,
722                                  $variable = variable),
723                   target);
725 function describe_variable_new_buffer (I) {
726     describe_variable(I.buffer,
727                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
728                       OPEN_NEW_BUFFER);
730 function describe_variable_new_window (I) {
731     describe_variable(I.buffer,
732                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
733                       OPEN_NEW_WINDOW);
735 interactive("describe-variable", null,
736             alternates(describe_variable_new_buffer, describe_variable_new_window));
741  * Describe Preference
742  */
744 function describe_preference (buffer, preference, target) {
745     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
746     let url = "http://kb.mozillazine.org/" + key;
747     browser_object_follow(buffer, target, url);
749 function describe_preference_new_buffer (I) {
750     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
751                         OPEN_NEW_BUFFER);
753 function describe_preference_new_window (I) {
754     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
755                         OPEN_NEW_WINDOW);
757 interactive("describe-preference", null,
758             alternates(describe_preference_new_buffer, describe_preference_new_window));
760 provide("help");