minibuffer-completion.js: Avoid some minor errors
[conkeror.git] / modules / help.js
blob8b44d6d77d758cc517997cf5b706028996be5672
1 require("special-buffer.js");
2 require("interactive.js");
4 function help_buffer(window, element) {
5     keywords(arguments);
6     conkeror.buffer.call(this, window, element, forward_keywords(arguments));
9 help_buffer.prototype = {
10     constructor: help_buffer,
12     __proto__: special_buffer.prototype
15 function where_is_command(buffer, command) {
16     var list = find_command_in_keymap(buffer, 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", 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 */, false /* allow untrusted */);
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 */, false /* allow untrusted */);
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/content/help.css");
128     }
131 define_keywords("$binding_list");
132 function describe_bindings_buffer(window, element) {
133     this.constructor_begin();
134     keywords(arguments);
135     special_buffer.call(this, window, element, forward_keywords(arguments));
136     this.binding_list = arguments.$binding_list;
137     this.constructor_end();
140 describe_bindings_buffer.prototype = {
142     get keymap() {
143         return help_buffer_keymap;
144     },
146     title : "Key bindings",
148     description : "*bindings*",
150     generate : function () {
151         var d = this.document;
152         var list = this.binding_list;
153         delete this.binding_list;
155         var list_by_keymap = {};
156         var keymap_list = [];
157         for each (let x in list) {
158             let name = x.bound_in || "";
159             let km;
160             if (name in list_by_keymap)
161                 km = list_by_keymap[name];
162             else {
163                 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
164                 keymap_list.push(km);
165             }
166             let catname = x.category || "";
167             let cat;
168             if (catname in km.list_by_category)
169                 cat = km.list_by_category[catname];
170             else {
171                 cat = km.list_by_category[catname] = [];
172                 cat.name = catname;
173                 if (catname == "")
174                     km.category_list.unshift(cat);
175                 else
176                     km.category_list.push(cat);
177             }
178             cat.push(x);
179         }
181         var g = new help_document_generator(d, this);
182         g.add_help_stylesheet();
184         d.body.setAttribute("class", "help-list");
186         for each (let km in keymap_list) {
187             g.text(km.name, g.element("h1", d.body));
188             for each (let cat in km.category_list) {
189                 if (cat.name != "")
190                     g.text(cat.name, g.element("h2", d.body));
192                 let table = g.element("table", d.body);
193                 for (var i = 0; i < cat.length; ++i) {
194                     let bind = cat[i];
195                     let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
196                     let seq_td = g.element("td", tr, "class", "key-binding");
197                     g.text(bind.seq, seq_td);
198                     let command_td = g.element("td", tr, "class", "command");
199                     let help_str = null;
200                     if (bind.command != null) {
201                         if (typeof(bind.command) == "function") {
202                             g.text("[function]", command_td);
203                         } else {
204                             g.text(bind.command, command_td);
205                             let cmd = interactive_commands.get(bind.command);
206                             if (cmd != null)
207                                 help_str = cmd.shortdoc;
208                         }
209                     }
210                     else if (bind.fallthrough)
211                         g.text("[pass through]", command_td);
212                     let help_td = g.element("td", tr, "class", "help");
213                     g.text(help_str || "", help_td);
214                 }
215             }
216         }
217     },
219     __proto__: special_buffer.prototype
223 function describe_bindings(buffer, target) {
224     var list = [];
225     for_each_key_binding(buffer, function (binding_stack) {
226             var last = binding_stack[binding_stack.length - 1];
227             if (last.command == null && !last.fallthrough)
228                 return;
229             let bound_in = null;
230         outer:
231             for (let i = binding_stack.length - 1; i >= 0; --i) {
232                 bound_in = binding_stack[i].bound_in;
233                 while (bound_in) {
234                     if (bound_in.name)
235                         break outer;
236                     bound_in = bound_in.bound_in;
237                 }
238             }
239             var bind = {seq: format_binding_sequence(binding_stack),
240                         fallthrough: last.fallthrough,
241                         command: last.command,
242                         bound_in: bound_in.name,
243                         category: last.category
244                        };
245             list.push(bind);
246         });
247     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
248                                                 $configuration = buffer.configuration,
249                                                 $binding_list = list),
250                   target);
252 interactive("describe-bindings", function (I) {describe_bindings(I.buffer, I.browse_target("describe-bindings"));});
253 default_browse_targets["describe-bindings"] = "find-url";
256 define_keywords("$command_list");
257 function apropos_command_buffer(window, element) {
258     this.constructor_begin();
259     keywords(arguments);
260     special_buffer.call(this, window, element, forward_keywords(arguments));
261     this.command_list = arguments.$command_list;
262     this.constructor_end();
265 apropos_command_buffer.prototype = {
267     get keymap() {
268         return help_buffer_keymap;
269     },
271     title : "Apropos commands",
273     description : "*Apropos*",
275     generate : function () {
276         var d = this.document;
277         var list = this.command_list;
278         delete this.command_list;
280         var g = new help_document_generator(d, this);
281         g.add_help_stylesheet();
283         d.body.setAttribute("class", "help-list");
285         var table = d.createElementNS(XHTML_NS, "table");
286         for (var i = 0; i < list.length; ++i) {
287             var binding = list[i];
288             var tr = d.createElementNS(XHTML_NS, "tr");
289             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
291             var command_td = d.createElementNS(XHTML_NS,"td");
292             g.command_reference(binding.name, command_td);
294             var shortdoc = "";
295             if (binding.cmd.shortdoc != null)
296                 shortdoc = binding.cmd.shortdoc;
297             tr.appendChild(command_td);
299             var shortdoc_td = d.createElementNS(XHTML_NS, "td");
300             shortdoc_td.setAttribute("class", "help");
301             shortdoc_td.textContent = shortdoc;
302             tr.appendChild(shortdoc_td);
304             table.appendChild(tr);
305         }
306         d.body.appendChild(table);
307     },
309     __proto__: special_buffer.prototype
313 /* TODO: support regexps/etc. */
314 function apropos_command(buffer, substring, target) {
315     var list = [];
316     interactive_commands.for_each(function (name, cmd) {
317         if (name.indexOf(substring) != -1) {
318             var binding = {name: name, cmd: cmd};
319             list.push(binding);
320         }
321     });
322     list.sort(function (a,b) {
323                   if (a.name < b.name)
324                       return -1;
325                   if (a.name > b.name)
326                       return 1;
327                   return 0
328               });
329     create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
330                                                 $configuration = buffer.configuration,
331                                                 $command_list = list),
332                   target);
335 interactive("apropos-command", "List commands whose names contain a given substring.",
336     function (I) {
337         apropos_command(I.buffer,
338                     (yield I.minibuffer.read($prompt = "Apropos command:",
339                                              $history = "apropos")),
340                     I.browse_target("apropos-command"));
342 default_browse_targets["apropos-command"] = "find-url";
346 define_keywords("$command", "$bindings");
347 function describe_command_buffer(window, element) {
348     this.constructor_begin();
349     keywords(arguments);
350     special_buffer.call(this, window, element, forward_keywords(arguments));
351     this.bindings = arguments.$bindings;
352     this.command = arguments.$command;
353     this.cmd = interactive_commands.get(this.command);
354     this.source_code_reference = this.cmd.source_code_reference;
355     this.constructor_end();
358 describe_command_buffer.prototype = {
360     get keymap() {
361         return help_buffer_keymap;
362     },
364     get title() { return "Command help: " + this.command; },
366     description : "*help*",
368     generate : function () {
369         var d = this.document;
371         var g = new help_document_generator(d, this);
373         g.add_help_stylesheet();
374         d.body.setAttribute("class", "describe-command");
376         var p;
378         p = g.element("p", d.body);
379         g.command_reference(this.command, p);
380         var cmd = interactive_commands.get(this.command);
381         if (cmd.source_code_reference)  {
382             g.text(" is an interactive command in ", p);
383             g.source_code_reference(cmd.source_code_reference, p);
384             g.text(".", p);
385         } else {
386             g.text(" is an interactive command.", p);
387         }
389         if (this.bindings.length > 0) {
390             p = g.element("p", d.body);
391             g.text("It is bound to ", p);
392             for (var i = 0; i < this.bindings.length; ++i) {
393                 if (i != 0)
394                     g.text(", ", p);
395                 g.key_binding(this.bindings[i], p);
396             }
397             g.text(".", p);
398         }
400         if (cmd.doc != null)
401             g.help_text(cmd.doc, d.body);
402     },
404     __proto__: special_buffer.prototype
408 function describe_command(buffer, command, target) {
409     var bindings = find_command_in_keymap(buffer, command);
410     create_buffer(buffer.window,
411                   buffer_creator(describe_command_buffer,
412                                  $configuration = buffer.configuration,
413                                  $command = command,
414                                  $bindings = bindings),
415                   target);
417 interactive("describe-command", function (I) {
418     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
419                      I.browse_target("describe-command"));
421 default_browse_targets["describe-command"] = "find-url";
428 function view_referenced_source_code(buffer) {
429     if (buffer.source_code_reference == null)
430         throw interactive_error("Command not valid in current buffer.");
431     yield buffer.source_code_reference.open_in_editor();
433 interactive("view-referenced-source-code", function (I) {yield view_referenced_source_code(I.buffer);});
436 define_keywords("$binding", "$other_bindings", "$key_sequence");
437 function describe_key_buffer(window, element) {
438     this.constructor_begin();
439     keywords(arguments);
440     special_buffer.call(this, window, element, forward_keywords(arguments));
441     this.key_sequence = arguments.$key_sequence;
442     this.bindings = arguments.$other_bindings;
443     this.bind = arguments.$binding;
444     this.source_code_reference = this.bind.source_code_reference;
445     this.constructor_end();
448 describe_key_buffer.prototype = {
450     get keymap() {
451         return help_buffer_keymap;
452     },
454     get title() { return "Key help: " + this.key_sequence; },
456     description : "*help*",
458     generate : function () {
459         var d = this.document;
461         var g = new help_document_generator(d, this);
463         g.add_help_stylesheet();
464         d.body.setAttribute("class", "describe-key");
466         var p;
468         p = g.element("p", d.body);
469         g.key_binding(this.key_sequence, p);
470         g.text(" is bound to the command ", p);
471         var command = this.bind.command;
472         if (command == null)
473             g.command_name("[pass through]", p);
474         else
475             g.command_reference(command, p);
476         if (this.source_code_reference) {
477             g.text(" in ", p);
478             g.source_code_reference(this.source_code_reference, p);
479         }
480         g.text(".", p);
482         if (command != null) {
483             p = g.element("p", d.body);
484             g.command_reference(command, p);
485             var cmd = interactive_commands.get(command);
486             if (cmd.source_code_reference)  {
487                 g.text(" is an interactive command in ", p);
488                 g.source_code_reference(cmd.source_code_reference, p);
489                 g.text(".", p);
490             } else {
491                 g.text(" is an interactive command.", p);
492             }
494             if (this.bindings.length > 0) {
495                 p = g.element("p", d.body);
496                 g.text("It is bound to ", p);
497                 for (var i = 0; i < this.bindings.length; ++i) {
498                     if (i != 0)
499                         g.text(", ", p);
500                     g.key_binding(this.bindings[i], p);
501                 }
502                 g.text(".", p);
503             }
505             if (cmd.doc != null)
506                 g.help_text(cmd.doc, d.body);
507         }
508     },
510     __proto__: special_buffer.prototype
514 function describe_key(buffer, key_info, target) {
515     var bindings;
516     var seq = key_info[0];
517     var bind = key_info[1];
519     if (bind.command)
520         bindings = find_command_in_keymap(buffer, bind.command);
521     else
522         bindings = [];
524     create_buffer(buffer.window,
525                   buffer_creator(describe_key_buffer,
526                                  $configuration = buffer.configuration,
527                                  $key_sequence = seq.join(" "),
528                                  $other_bindings = bindings,
529                                  $binding = bind),
530                   target);
533 function describe_key_briefly(buffer, key_info) {
534     var bindings;
535     var seq = key_info[0];
536     var bind = key_info[1];
538     buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command);
541 interactive("describe-key", function (I) {
542     describe_key(I.buffer,
543                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
544                  I.browse_target("describe-key"));
546 interactive("describe-key-briefly", function (I) {
547     describe_key_briefly(I.buffer,
548                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
550 default_browse_targets["describe-key"] = "find-url";
555 define_keywords("$variable");
556 function describe_variable_buffer(window, element) {
557     this.constructor_begin();
558     keywords(arguments);
559     special_buffer.call(this, window, element, forward_keywords(arguments));
560     this.bindings = arguments.$bindings;
561     this.variable = arguments.$variable;
562     this.cmd = user_variables.get(this.variable);
563     this.source_code_reference = this.cmd.source_code_reference;
564     this.constructor_end();
567 function pretty_print_value(value) {
568     if (value === undefined)
569         return "undefined";
570     if (value === null)
571         return "null";
572     if (typeof(value) == "object")
573         return value.toSource();
574     if (typeof(value) == "function")
575         return value.toString();
576     if (typeof(value) == "string") {
577         let s = value.toSource();
578         // toSource returns: (new String("<blah>"))
579         // we want just: "<blah>"
580         return s.substring(12, s.length - 2);
581     }
582     return new String(value);
585 describe_variable_buffer.prototype = {
587     get keymap() {
588         return help_buffer_keymap;
589     },
591     get title() { return "Variable help: " + this.variable; },
593     description : "*help*",
595     generate : function () {
596         var d = this.document;
598         var g = new help_document_generator(d, this);
600         g.add_help_stylesheet();
601         d.body.setAttribute("class", "describe-variable");
603         var p;
605         p = g.element("p", d.body);
606         g.variable_reference(this.variable, p);
607         var uvar = user_variables.get(this.variable);
608         if (uvar.source_code_reference)  {
609             g.text(" is a user variable in ", p);
610             g.source_code_reference(uvar.source_code_reference, p);
611             g.text(".", p);
612         } else {
613             g.text(" is a user variable.", p);
614         }
616         p = g.element("p", d.body);
617         g.text("Its value is: ", p);
618         {
619             let s = pretty_print_value(conkeror[this.variable]);
620             let pre = g.element("pre", p);
621             g.text(s, pre);
622         }
624         if (uvar.doc != null)
625             g.help_text(uvar.doc, d.body);
627         p = g.element("p", d.body);
628         g.text("Its default value is: ", p);
629         {
630             let s = pretty_print_value(user_variables.get(this.variable).default_value);
631             let pre = g.element("pre", p);
632             g.text(s, pre);
633         }
634     },
636     __proto__: special_buffer.prototype
640 function describe_variable(buffer, variable, target) {
641     create_buffer(buffer.window,
642                   buffer_creator(describe_variable_buffer,
643                                  $configuration = buffer.configuration,
644                                  $variable = variable),
645                   target);
647 interactive("describe-variable", function (I) {
648     describe_variable(I.buffer, (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
649                      I.browse_target("describe-variable"));
651 default_browse_targets["describe-variable"] = "find-url";