remove in_module mechanism
[conkeror.git] / modules / help.js
bloba6408720ccc23335fd1209f9825f481962497a02
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     constructor: help_document_generator,
35     __proto__: dom_generator.prototype,
37     key_binding: function (str, parent) {
38         var node = this.element("span", "class", "key-binding");
39         this.text(str, node);
40         if (parent)
41             parent.appendChild(node);
42         return node;
43     },
45     source_code_reference: function (ref, parent) {
46         var f = this.document.createDocumentFragment();
47         var module_name = ref.module_name;
48         var buffer = this.buffer;
49         //f.appendChild(this.text(module_name != null ? "module " : "file "));
50         var x = this.element("a",
51                              "class", "source-code-reference",
52                              "href", "javascript:");
53         x.addEventListener("click", function (event) {
54             co_call(function () {
55                 try {
56                     yield ref.open_in_editor();
57                 } catch (e) {
58                     handle_interactive_error(buffer.window, e);
59                 }}());
60             event.preventDefault();
61             event.stopPropagation();
62         }, false /* capture */);
63         x.textContent = (module_name != null ? module_name : ref.file_name);
64         f.appendChild(x);
65         if (parent)
66             parent.appendChild(f);
67         return f;
68     },
70     command_name: function (name, parent) {
71         var node = this.element("span", "class", "command");
72         this.text(name, node);
73         if (parent)
74             parent.appendChild(node);
75         return node;
76     },
78     command_reference: function (name, parent) {
79         var node = this.element("a",
80                                 "class", "command",
81                                 "href", "javascript:");
82         var buffer = this.buffer;
83         node.addEventListener("click", function (event) {
84                                   /* FIXME: don't hardcode browse target */
85                                   describe_command(buffer, name, OPEN_NEW_BUFFER);
86                                   event.preventDefault();
87                                   event.stopPropagation();
88             }, false /* capture */);
89         this.text(name, node);
90         if (parent)
91             parent.appendChild(node);
92         return node;
93     },
95     variable_reference: function (name, parent) {
96         var node = this.element("a", "class", "variable", "href", "#");
97         /* FIXME: make this work */
98         this.text(name, node);
99         if (parent)
100             parent.appendChild(node);
101         return node;
102     },
104     help_text: function (str, parent) {
105         var paras = str.split("\n");
106         var f = this.document.createDocumentFragment();
107         for (var i = 0; i < paras.length; ++i) {
108             var para = paras[i];
109             if (para.length == 0)
110                 continue;
112             var p = this.element("p", f);
114             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
116             var match;
117             var last_index = 0;
118             while ((match = regexp.exec(para)) != null) {
119                 this.text(para.substring(last_index, match.index), p);
120                 var command = match[1];
121                 /* FIXME: check if it is a valid command */
122                 this.command_reference(command, p);
123                 last_index = regexp.lastIndex;
124             }
125             if (last_index < para.length)
126                 this.text(para.substring(last_index), p);
127         }
128         if (parent != null)
129             parent.appendChild(f);
130         return f;
131     },
133     add_help_stylesheet: function () {
134         this.add_stylesheet("chrome://conkeror-gui/content/help.css");
135     }
139 function help_buffer_modality (buffer, element) {
140     buffer.keymaps.push(help_buffer_keymap);
144  * Describe Bindings
145  */
147 define_keywords("$binding_list");
148 function describe_bindings_buffer (window) {
149     this.constructor_begin();
150     keywords(arguments);
151     special_buffer.call(this, window, forward_keywords(arguments));
152     this.binding_list = arguments.$binding_list;
153     this.modalities.push(help_buffer_modality);
154     this.constructor_end();
156 describe_bindings_buffer.prototype = {
157     constructor: describe_bindings_buffer,
158     title: "Key bindings",
160     description: "*bindings*",
162     generate: function () {
163         var d = this.document;
164         var list = this.binding_list;
165         delete this.binding_list;
167         var list_by_keymap = {};
168         var keymap_list = [];
169         for each (let x in list) {
170             let name = x.bound_in || "";
171             let km;
172             if (name in list_by_keymap)
173                 km = list_by_keymap[name];
174             else {
175                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
176                 keymap_list.push(km);
177             }
178             let catname = x.category || "";
179             let cat;
180             if (catname in km.list_by_category)
181                 cat = km.list_by_category[catname];
182             else {
183                 cat = km.list_by_category[catname] = [];
184                 cat.name = catname;
185                 if (catname == "")
186                     km.category_list.unshift(cat);
187                 else
188                     km.category_list.push(cat);
189             }
190             cat.push(x);
191         }
193         var g = new help_document_generator(d, this);
194         g.add_help_stylesheet();
196         d.body.setAttribute("class", "help-list");
198         for each (let km in keymap_list) {
199             g.text(km.name, g.element("h1", d.body));
200             for each (let cat in km.category_list) {
201                 if (cat.name != "")
202                     g.text(cat.name, g.element("h2", d.body));
204                 let table = g.element("table", d.body);
205                 for (var i = 0; i < cat.length; ++i) {
206                     let bind = cat[i];
207                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
208                     let seq_td = g.element("td", tr, "class", "key-binding");
209                     g.text(bind.seq, seq_td);
210                     let command_td = g.element("td", tr, "class", "command");
211                     let help_str = null;
212                     if (bind.command != null) {
213                         if (typeof(bind.command) == "function") {
214                             g.text("[function]", command_td);
215                         } else {
216                             let cmd = interactive_commands.get(bind.command);
217                             if (cmd != null) {
218                                 g.command_reference(cmd.name, command_td);
219                                 help_str = cmd.shortdoc;
220                             } else {
221                                 g.text(bind.command, command_td);
222                             }
223                         }
224                     } else if (bind.keymap != null) {
225                         g.text("["+bind.keymap+"]", command_td);
226                     } else if (bind.fallthrough)
227                         g.text("[pass through]", command_td);
228                     let help_td = g.element("td", tr, "class", "help");
229                     g.text(help_str || "", help_td);
230                 }
231             }
232         }
233     },
235     __proto__: special_buffer.prototype
239 function describe_bindings (buffer, target, keymaps, prefix) {
240     var list = [];
241     if (! keymaps)
242         keymaps = get_current_keymaps(buffer.window);
243     if (prefix)
244         prefix = format_binding_sequence(
245             prefix.map(function (x) { return {key:x}; }))+" ";
246     else
247         prefix = "";
248     for_each_key_binding(keymaps, function (binding_stack) {
249             var last = binding_stack[binding_stack.length - 1];
250             //we don't care about auto-generated keymap bindings.
251             if (last.keymap && last.keymap.anonymous)
252                 return;
253             let bound_in = null;
254         outer:
255             for (let i = binding_stack.length - 1; i >= 0; --i) {
256                 bound_in = binding_stack[i].bound_in;
257                 while (bound_in) {
258                     if (bound_in.name && !bound_in.anonymous)
259                         break outer;
260                     bound_in = bound_in.bound_in;
261                 }
262             }
263             var keymap = null;
264             if (last.keymap && ! last.keymap.anonymous)
265                 keymap = last.keymap.name;
266             var bind = {seq: prefix+format_binding_sequence(binding_stack),
267                         fallthrough: last.fallthrough,
268                         command: last.command,
269                         keymap: keymap,
270                         bound_in: bound_in.name,
271                         category: last.category
272                        };
273             list.push(bind);
274         });
275     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
276                                                 $opener = buffer,
277                                                 $binding_list = list),
278                   target);
280 function describe_bindings_new_buffer (I) {
281     describe_bindings(I.buffer, OPEN_NEW_BUFFER);
283 function describe_bindings_new_window (I) {
284     describe_bindings(I.buffer, OPEN_NEW_WINDOW);
286 interactive("describe-bindings",
287     "Show a help buffer describing the bindings in the context keymaps, "+
288     "meaning the top-level keymaps according to the focus context in the "+
289     "current buffer.",
290     alternates(describe_bindings_new_buffer,
291                describe_bindings_new_window));
293 function describe_active_bindings_new_buffer (I) {
294     describe_bindings(I.buffer, OPEN_NEW_BUFFER,
295                       I.keymaps || get_current_keymaps(I.buffer.window),
296                       I.key_sequence.slice(0, -1));
298 function describe_active_bindings_new_window (I) {
299     describe_bindings(I.buffer, OPEN_NEW_WINDOW,
300                       I.keymaps || get_current_keymaps(I.buffer.window),
301                       I.key_sequence.slice(0, -1));
303 interactive("describe-active-bindings",
304     "Show a help buffer describing the bindings in the active keymaps, "+
305     "meaning the keymaps in the middle of an ongoing key sequence.  This "+
306     "command is intended to be called via `sequence_help_keymap'.  For "+
307     "that reason, `describe-active-bindings' does not consume and prefix "+
308     "commands like `universal-argument', as doing so would lead to "+
309     "ambiguities with respect to the intent of the user.",
310     describe_active_bindings_new_buffer);
314  * Apropos Command
315  */
317 define_keywords("$command_list");
318 function apropos_command_buffer (window) {
319     this.constructor_begin();
320     keywords(arguments);
321     special_buffer.call(this, window, forward_keywords(arguments));
322     this.command_list = arguments.$command_list;
323     this.modalities.push(help_buffer_modality);
324     this.constructor_end();
326 apropos_command_buffer.prototype = {
327     constructor: apropos_command_buffer,
328     title: "Apropos commands",
330     description: "*Apropos*",
332     generate: function () {
333         var d = this.document;
334         var list = this.command_list;
335         delete this.command_list;
337         var g = new help_document_generator(d, this);
338         g.add_help_stylesheet();
340         d.body.setAttribute("class", "help-list");
342         var table = d.createElementNS(XHTML_NS, "table");
343         for (var i = 0; i < list.length; ++i) {
344             var binding = list[i];
345             var tr = d.createElementNS(XHTML_NS, "tr");
346             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
348             var command_td = d.createElementNS(XHTML_NS,"td");
349             g.command_reference(binding.name, command_td);
351             var shortdoc = "";
352             if (binding.cmd.shortdoc != null)
353                 shortdoc = binding.cmd.shortdoc;
354             tr.appendChild(command_td);
356             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
357             shortdoc_td.setAttribute("class", "help");
358             shortdoc_td.textContent = shortdoc;
359             tr.appendChild(shortdoc_td);
361             table.appendChild(tr);
362         }
363         d.body.appendChild(table);
364     },
366     __proto__: special_buffer.prototype
370 /* TODO: support regexps/etc. */
371 function apropos_command (buffer, substring, target) {
372     var list = [];
373     interactive_commands.for_each(function (name, cmd) {
374         if (name.indexOf(substring) != -1) {
375             var binding = {name: name, cmd: cmd};
376             list.push(binding);
377         }
378     });
379     list.sort(function (a,b) {
380                   if (a.name < b.name)
381                       return -1;
382                   if (a.name > b.name)
383                       return 1;
384                   return 0
385               });
386     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
387                                                 $opener = buffer,
388                                                 $command_list = list),
389                   target);
392 function apropos_command_new_buffer (I) {
393     apropos_command(I.buffer,
394                     (yield I.minibuffer.read($prompt = "Apropos command:",
395                                              $history = "apropos")),
396                     OPEN_NEW_BUFFER);
398 function apropos_command_new_window (I) {
399     apropos_command(I.buffer,
400                     (yield I.minibuffer.read($prompt = "Apropos command:",
401                                              $history = "apropos")),
402                     OPEN_NEW_WINDOW);
404 interactive("apropos-command", "List commands whose names contain a given substring.",
405             alternates(apropos_command_new_buffer, apropos_command_new_window));
410  * Describe Command
411  */
413 define_keywords("$command", "$bindings");
414 function describe_command_buffer (window) {
415     this.constructor_begin();
416     keywords(arguments);
417     special_buffer.call(this, window, forward_keywords(arguments));
418     this.bindings = arguments.$bindings;
419     this.command = arguments.$command;
420     this.cmd = interactive_commands.get(this.command);
421     this.source_code_reference = this.cmd.source_code_reference;
422     this.modalities.push(help_buffer_modality);
423     this.constructor_end();
425 describe_command_buffer.prototype = {
426     constructor: describe_command_buffer,
427     get title () { return "Command help: " + this.command; },
429     description: "*help*",
431     generate: function () {
432         var d = this.document;
434         var g = new help_document_generator(d, this);
436         g.add_help_stylesheet();
437         d.body.setAttribute("class", "describe-command");
439         var p;
441         p = g.element("p", d.body);
442         g.command_reference(this.command, p);
443         var cmd = interactive_commands.get(this.command);
444         if (cmd.source_code_reference)  {
445             g.text(" is an interactive command in ", p);
446             g.source_code_reference(cmd.source_code_reference, p);
447             g.text(".", p);
448         } else {
449             g.text(" is an interactive command.", p);
450         }
452         if (this.bindings.length > 0) {
453             p = g.element("p", d.body);
454             g.text("It is bound to ", p);
455             for (var i = 0; i < this.bindings.length; ++i) {
456                 if (i != 0)
457                     g.text(", ", p);
458                 g.key_binding(this.bindings[i], p);
459             }
460             g.text(".", p);
461         }
463         if (cmd.doc != null)
464             g.help_text(cmd.doc, d.body);
465     },
467     __proto__: special_buffer.prototype
471 function describe_command (buffer, command, target) {
472     var keymaps = get_current_keymaps(buffer.window);
473     var bindings = keymap_lookup_command(keymaps, command);
474     create_buffer(buffer.window,
475                   buffer_creator(describe_command_buffer,
476                                  $opener = buffer,
477                                  $command = command,
478                                  $bindings = bindings),
479                   target);
481 function describe_command_new_buffer (I) {
482     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
483                      OPEN_NEW_BUFFER);
485 function describe_command_new_window (I) {
486     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
487                      OPEN_NEW_WINDOW);
489 interactive("describe-command", null,
490             alternates(describe_command_new_buffer, describe_command_new_window));
494 function view_referenced_source_code (buffer) {
495     if (buffer.source_code_reference == null)
496         throw interactive_error("Command not valid in current buffer.");
497     yield buffer.source_code_reference.open_in_editor();
499 interactive("view-referenced-source-code", null,
500             function (I) {yield view_referenced_source_code(I.buffer);});
504  * Describe Key
505  */
507 define_keywords("$binding", "$other_bindings", "$key_sequence");
508 function describe_key_buffer (window) {
509     this.constructor_begin();
510     keywords(arguments);
511     special_buffer.call(this, window, forward_keywords(arguments));
512     this.key_sequence = arguments.$key_sequence;
513     this.bindings = arguments.$other_bindings;
514     this.bind = arguments.$binding;
515     this.source_code_reference = this.bind.source_code_reference;
516     this.modalities.push(help_buffer_modality);
517     this.constructor_end();
519 describe_key_buffer.prototype = {
520     constructor: describe_key_buffer,
521     get title () { return "Key help: " + this.key_sequence; },
523     description: "*help*",
525     generate: function () {
526         var d = this.document;
528         var g = new help_document_generator(d, this);
530         g.add_help_stylesheet();
531         d.body.setAttribute("class", "describe-key");
533         var p;
535         p = g.element("p", d.body);
536         g.key_binding(this.key_sequence, p);
537         g.text(" is bound to the command ", p);
538         var command = this.bind.command;
539         if (command == null)
540             g.command_name("[pass through]", p);
541         else
542             g.command_reference(command, p);
543         if (this.bind.browser_object != null) {
544             g.text(" with the browser object, ", p);
545             if (this.bind.browser_object instanceof Function) {
546                 g.text("<anonymous browser-object function>", p);
547             } else if (this.bind.browser_object instanceof browser_object_class) {
548                 g.text(this.bind.browser_object.name, p);
549             } else if (typeof(this.bind.browser_object) == "string") {
550                 g.text('"'+this.bind.browser_object+'"', p);
551             } else {
552                 g.text(this.bind.browser_object, p);
553             }
554         }
555         if (this.source_code_reference) {
556             g.text(" in ", p);
557             g.source_code_reference(this.source_code_reference, p);
558         }
559         g.text(".", p);
561         if (command != null) {
562             p = g.element("p", d.body);
563             g.command_reference(command, p);
564             var cmd = interactive_commands.get(command);
565             if (cmd.source_code_reference)  {
566                 g.text(" is an interactive command in ", p);
567                 g.source_code_reference(cmd.source_code_reference, p);
568                 g.text(".", p);
569             } else {
570                 g.text(" is an interactive command.", p);
571             }
573             if (this.bindings.length > 0) {
574                 p = g.element("p", d.body);
575                 g.text("It is bound to ", p);
576                 for (var i = 0; i < this.bindings.length; ++i) {
577                     if (i != 0)
578                         g.text(", ", p);
579                     g.key_binding(this.bindings[i], p);
580                 }
581                 g.text(".", p);
582             }
584             if (cmd.doc != null)
585                 g.help_text(cmd.doc, d.body);
586         }
587     },
589     __proto__: special_buffer.prototype
593 function describe_key (buffer, key_info, target) {
594     var bindings;
595     var seq = key_info[0];
596     var bind = key_info[1];
597     var keymaps = get_current_keymaps(buffer.window);
598     if (bind.command)
599         bindings = keymap_lookup_command(keymaps, bind.command);
600     else
601         bindings = [];
602     create_buffer(buffer.window,
603                   buffer_creator(describe_key_buffer,
604                                  $opener = buffer,
605                                  $key_sequence = seq.join(" "),
606                                  $other_bindings = bindings,
607                                  $binding = bind),
608                   target);
610 function describe_key_new_buffer (I) {
611     describe_key(I.buffer,
612                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:")),
613                  OPEN_NEW_BUFFER);
615 function describe_key_new_window (I) {
616     describe_key(I.buffer,
617                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:")),
618                  OPEN_NEW_WINDOW);
621 function describe_key_briefly (buffer, key_info) {
622     var bindings;
623     var seq = key_info[0];
624     var bind = key_info[1];
625     var browser_object = "";
626     if (bind.browser_object != null) {
627         browser_object += " on the browser object, ";
628         if (bind.browser_object instanceof Function) {
629             browser_object += "<anonymous browser-object function>";
630         } else if (bind.browser_object instanceof browser_object_class) {
631             browser_object += bind.browser_object.name;
632         } else if (typeof(bind.browser_object) == "string") {
633             browser_object += '"'+bind.browser_object+'"';
634         } else {
635             browser_object += bind.browser_object;
636         }
637     }
638     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command + browser_object);
641 interactive("describe-key", null,
642             alternates(describe_key_new_buffer, describe_key_new_window));
644 interactive("describe-key-briefly", null,
645     function (I) {
646         describe_key_briefly(
647             I.buffer,
648             (yield I.minibuffer.read_key_binding($prompt = "Describe key:")));
649     });
654  * Describe Variable
655  */
657 define_keywords("$variable");
658 function describe_variable_buffer (window) {
659     this.constructor_begin();
660     keywords(arguments);
661     special_buffer.call(this, window, forward_keywords(arguments));
662     this.variable = arguments.$variable;
663     this.cmd = user_variables[this.variable];
664     this.source_code_reference = this.cmd.source_code_reference;
665     this.modalities.push(help_buffer_modality);
666     this.constructor_end();
668 describe_variable_buffer.prototype = {
669     constructor: describe_variable_buffer,
670     get title () { return "Variable help: " + this.variable; },
672     description: "*help*",
674     generate: function () {
675         var d = this.document;
677         var g = new help_document_generator(d, this);
679         g.add_help_stylesheet();
680         d.body.setAttribute("class", "describe-variable");
682         var p;
684         p = g.element("p", d.body);
685         g.variable_reference(this.variable, p);
686         var uvar = user_variables[this.variable];
687         if (uvar.source_code_reference)  {
688             g.text(" is a user variable in ", p);
689             g.source_code_reference(uvar.source_code_reference, p);
690             g.text(".", p);
691         } else {
692             g.text(" is a user variable.", p);
693         }
695         p = g.element("p", d.body);
696         g.text("Its value is: ", p);
697         let value = conkeror[this.variable];
698         {
699             let s = pretty_print_value(value);
700             let pre = g.element("pre", p);
701             g.text(s, pre);
702         }
704         if (uvar.doc != null)
705             g.help_text(uvar.doc, d.body);
707         if (uvar.default_value !== undefined &&
708             (uvar.default_value !== value ||
709              (typeof(uvar.default_value) != "object")))  {
710             p = g.element("p", d.body);
711             g.text("Its default value is: ", p);
712             {
713                 let s = pretty_print_value(uvar.default_value);
714                 let pre = g.element("pre", p);
715                 g.text(s, pre);
716             }
717         }
718     },
720     __proto__: special_buffer.prototype
724 function describe_variable (buffer, variable, target) {
725     create_buffer(buffer.window,
726                   buffer_creator(describe_variable_buffer,
727                                  $opener = buffer,
728                                  $variable = variable),
729                   target);
731 function describe_variable_new_buffer (I) {
732     describe_variable(I.buffer,
733                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
734                       OPEN_NEW_BUFFER);
736 function describe_variable_new_window (I) {
737     describe_variable(I.buffer,
738                       (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
739                       OPEN_NEW_WINDOW);
741 interactive("describe-variable", null,
742             alternates(describe_variable_new_buffer, describe_variable_new_window));
747  * Describe Preference
748  */
750 function describe_preference (buffer, preference, target) {
751     let key = preference.charAt(0).toUpperCase() + preference.substring(1);
752     let url = "http://kb.mozillazine.org/" + key;
753     browser_object_follow(buffer, target, url);
755 function describe_preference_new_buffer (I) {
756     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
757                         OPEN_NEW_BUFFER);
759 function describe_preference_new_window (I) {
760     describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
761                         OPEN_NEW_WINDOW);
763 interactive("describe-preference", null,
764             alternates(describe_preference_new_buffer, describe_preference_new_window));
766 provide("help");