Add support for documenting user variables.
[conkeror.git] / modules / help.js
blob7d5ba805b515adb975bb98e63c546667c0678b9e
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) {
30     dom_generator.call(this, document, XHTML_NS);
32 help_document_generator.prototype = {
33     __proto__: dom_generator.prototype,
35     key_binding : function(str, parent) {
36         var node = this.element("span", "class", "key-binding");
37         this.text(str, node);
38         if (parent)
39             parent.appendChild(node);
40         return node;
41     },
43     source_code_reference : function(ref, parent) {
44         var f = this.document.createDocumentFragment();
45         var module_name = ref.module_name;
46         //f.appendChild(this.text(module_name != null ? "module " : "file "));
47         var x = this.element("a",
48                              "class", "source-code-reference",
49                              "href", "javascript:");
50         x.addEventListener("click", function (event) {
51             co_call(ref.open_in_editor());
52             event.preventDefault();
53             event.stopPropagation();
54         }, false /* capture */, false /* allow untrusted */);
55         x.textContent = (module_name != null ? module_name : ref.file_name);
56         f.appendChild(x);
57         if (parent)
58             parent.appendChild(f);
59         return f;
60     },
62     command_name : function(name, parent) {
63         var node = this.element("span", "class", "command");
64         this.text(name, node);
65         if (parent)
66             parent.appendChild(node);
67         return node;
68     },
70     command_reference : function(name, parent) {
71         var node = this.element("a", "class", "command", "href", "#");
72         /* FIXME: make this work */
73         this.text(name, node);
74         if (parent)
75             parent.appendChild(node);
76         return node;
77     },
79     variable_reference : function(name, parent) {
80         var node = this.element("a", "class", "variable", "href", "#");
81         /* FIXME: make this work */
82         this.text(name, node);
83         if (parent)
84             parent.appendChild(node);
85         return node;
86     },
88     help_text : function(str, parent) {
89         var paras = str.split("\n");
90         var f = this.document.createDocumentFragment();
91         for (var i = 0; i < paras.length; ++i) {
92             var para = paras[i];
93             if (para.length == 0)
94                 continue;
96             var p = this.element("p", f);
98             var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
100             var match;
101             var last_index = 0;
102             while ((match = regexp.exec(para)) != null) {
103                 this.text(para.substring(last_index, match.index), p);
104                 var command = match[1];
105                 /* FIXME: check if it is a valid command */
106                 this.command_reference(command, p);
107                 last_index = regexp.lastIndex;
108             }
109             if (last_index < para.length)
110                 this.text(para.substring(last_index), p);
111         }
112         if (parent != null)
113             parent.appendChild(f);
114         return f;
115     },
117     add_help_stylesheet : function () {
118         this.add_stylesheet("chrome://conkeror/content/help.css");
119     }
122 define_keywords("$binding_list");
123 function describe_bindings_buffer(window, element) {
124     this.constructor_begin();
125     keywords(arguments);
126     special_buffer.call(this, window, element, forward_keywords(arguments));
127     this.binding_list = arguments.$binding_list;
128     this.constructor_end();
131 describe_bindings_buffer.prototype = {
133     get keymap() {
134         return help_buffer_keymap;
135     },
137     title : "Key bindings",
139     description : "*bindings*",
141     generate : function () {
142         var d = this.top_document;
143         var list = this.binding_list;
144         delete this.binding_list;
146         var g = new help_document_generator(d);
147         g.add_help_stylesheet();
149         d.body.setAttribute("class", "describe-bindings");
151         var table = d.createElementNS(XHTML_NS, "table");
152         for (var i = 0; i < list.length; ++i) {
153             var bind = list[i];
154             var tr = d.createElementNS(XHTML_NS, "tr");
155             tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
156             var seq_td = d.createElementNS(XHTML_NS, "td");
157             seq_td.setAttribute("class", "key-binding");
158             seq_td.textContent = bind.seq;
159             tr.appendChild(seq_td);
160             var command_td = d.createElementNS(XHTML_NS,"td");
161             command_td.setAttribute("class", "command");
162             var help_str = null;
163             if (bind.command != null) {
164                 command_td.textContent = bind.command;
165                 var cmd = interactive_commands.get(bind.command);
166                 if (cmd != null)
167                     help_str = cmd.shortdoc;
168             }
169             else if (bind.fallthrough)
170                 command_td.textContent = "[pass through]";
171             tr.appendChild(command_td);
172             var help_td = d.createElementNS(XHTML_NS, "td");
173             help_td.setAttribute("class", "help");
174             help_td.textContent = help_str || "";
175             tr.appendChild(help_td);
176             table.appendChild(tr);
177         }
178         d.body.appendChild(table);
179     },
181     __proto__: special_buffer.prototype
185 function describe_bindings(buffer, target) {
186     var list = [];
187     for_each_key_binding(buffer, function (binding_stack) {
188             var last = binding_stack[binding_stack.length - 1];
189             if (last.command == null && !last.fallthrough)
190                 return;
191             var bind = {seq: format_binding_sequence(binding_stack),
192                         fallthrough: last.fallthrough,
193                         command: last.command};
194             list.push(bind);
195         });
196     create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
197                                                 $configuration = buffer.configuration,
198                                                 $binding_list = list),
199                   target);
201 interactive("describe-bindings", function (I) {describe_bindings(I.buffer, I.browse_target("describe-bindings"));});
202 default_browse_targets["describe-bindings"] = "find-url";
206 define_keywords("$command", "$bindings");
207 function describe_command_buffer(window, element) {
208     this.constructor_begin();
209     keywords(arguments);
210     special_buffer.call(this, window, element, forward_keywords(arguments));
211     this.bindings = arguments.$bindings;
212     this.command = arguments.$command;
213     this.cmd = interactive_commands.get(this.command);
214     this.source_code_reference = this.cmd.source_code_reference;
215     this.constructor_end();
218 describe_command_buffer.prototype = {
220     get keymap() {
221         return help_buffer_keymap;
222     },
224     get title() { return "Command help: " + this.command; },
226     description : "*help*",
228     generate : function () {
229         var d = this.top_document;
231         var g = new help_document_generator(d);
233         g.add_help_stylesheet();
234         d.body.setAttribute("class", "describe-command");
236         var p;
238         p = g.element("p", d.body);
239         g.command_reference(this.command, p);
240         var cmd = interactive_commands.get(this.command);
241         if (cmd.source_code_reference)  {
242             g.text(" is an interactive command in ", p);
243             g.source_code_reference(cmd.source_code_reference, p);
244             g.text(".", p);
245         } else {
246             g.text(" is an interactive command.", p);
247         }
249         if (this.bindings.length > 0) {
250             p = g.element("p", d.body);
251             g.text("It is bound to ", p);
252             for (var i = 0; i < this.bindings.length; ++i) {
253                 if (i != 0)
254                     g.text(", ", p);
255                 g.key_binding(this.bindings[i], p);
256             }
257             g.text(".", p);
258         }
260         if (cmd.doc != null)
261             g.help_text(cmd.doc, d.body);
262     },
264     __proto__: special_buffer.prototype
268 function describe_command(buffer, command, target) {
269     var bindings = find_command_in_keymap(buffer, command);
270     create_buffer(buffer.window,
271                   buffer_creator(describe_command_buffer,
272                                  $configuration = buffer.configuration,
273                                  $command = command,
274                                  $bindings = bindings),
275                   target);
277 interactive("describe-command", function (I) {
278     describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
279                      I.browse_target("describe-command"));
281 default_browse_targets["describe-command"] = "find-url";
288 function view_referenced_source_code(buffer) {
289     if (buffer.source_code_reference == null)
290         throw interactive_error("Command not valid in current buffer.");
291     yield buffer.source_code_reference.open_in_editor();
293 interactive("view-referenced-source-code", function (I) {yield view_referenced_source_code(I.buffer);});
296 define_keywords("$binding", "$other_bindings", "$key_sequence");
297 function describe_key_buffer(window, element) {
298     this.constructor_begin();
299     keywords(arguments);
300     special_buffer.call(this, window, element, forward_keywords(arguments));
301     this.key_sequence = arguments.$key_sequence;
302     this.bindings = arguments.$other_bindings;
303     this.bind = arguments.$binding;
304     this.source_code_reference = this.bind.source_code_reference;
305     this.constructor_end();
308 describe_key_buffer.prototype = {
310     get keymap() {
311         return help_buffer_keymap;
312     },
314     get title() { return "Key help: " + this.key_sequence; },
316     description : "*help*",
318     generate : function () {
319         var d = this.top_document;
321         var g = new help_document_generator(d);
323         g.add_help_stylesheet();
324         d.body.setAttribute("class", "describe-key");
326         var p;
328         p = g.element("p", d.body);
329         g.key_binding(this.key_sequence, p);
330         g.text(" is bound to the command ", p);
331         var command = this.bind.command;
332         if (command == null)
333             g.command_name("[pass through]", p);
334         else
335             g.command_reference(command, p);
336         if (this.source_code_reference) {
337             g.text(" in ", p);
338             g.source_code_reference(this.source_code_reference, p);
339         }
340         g.text(".", p);
342         if (command != null) {
343             p = g.element("p", d.body);
344             g.command_reference(command, p);
345             var cmd = interactive_commands.get(command);
346             if (cmd.source_code_reference)  {
347                 g.text(" is an interactive command in ", p);
348                 g.source_code_reference(cmd.source_code_reference, p);
349                 g.text(".", p);
350             } else {
351                 g.text(" is an interactive command.", p);
352             }
354             if (this.bindings.length > 0) {
355                 p = g.element("p", d.body);
356                 g.text("It is bound to ", p);
357                 for (var i = 0; i < this.bindings.length; ++i) {
358                     if (i != 0)
359                         g.text(", ", p);
360                     g.key_binding(this.bindings[i], p);
361                 }
362                 g.text(".", p);
363             }
365             if (cmd.doc != null)
366                 g.help_text(cmd.doc, d.body);
367         }
368     },
370     __proto__: special_buffer.prototype
374 function describe_key(buffer, key_info, target) {
375     var bindings;
376     var seq = key_info[0];
377     var bind = key_info[1];
379     if (bind.command)
380         bindings = find_command_in_keymap(buffer, bind.command);
381     else
382         bindings = [];
384     create_buffer(buffer.window,
385                   buffer_creator(describe_key_buffer,
386                                  $configuration = buffer.configuration,
387                                  $key_sequence = seq.join(" "),
388                                  $other_bindings = bindings,
389                                  $binding = bind),
390                   target);
392 interactive("describe-key", function (I) {
393     describe_key(I.buffer,
394                  (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
395                  I.browse_target("describe-key"));
397 default_browse_targets["describe-key"] = "find-url";
402 define_keywords("$variable");
403 function describe_variable_buffer(window, element) {
404     this.constructor_begin();
405     keywords(arguments);
406     special_buffer.call(this, window, element, forward_keywords(arguments));
407     this.bindings = arguments.$bindings;
408     this.variable = arguments.$variable;
409     this.cmd = interactive_variables.get(this.variable);
410     this.source_code_reference = this.cmd.source_code_reference;
411     this.constructor_end();
414 function pretty_print_value(value) {
415     if (value === undefined)
416         return "undefined";
417     if (value === null)
418         return "null";
419     if (typeof(value) == "object")
420         return value.toSource();
421     if (typeof(value) == "function")
422         return value.toString();
423     if (typeof(value) == "string") {
424         let s = value.toSource();
425         // toSource returns: (new String("<blah>"))
426         // we want just: "<blah>"
427         return s.substring(12, s.length - 2);
428     }
429     return new String(value);
432 describe_variable_buffer.prototype = {
434     get keymap() {
435         return help_buffer_keymap;
436     },
438     get title() { return "Variable help: " + this.variable; },
440     description : "*help*",
442     generate : function () {
443         var d = this.top_document;
445         var g = new help_document_generator(d);
447         g.add_help_stylesheet();
448         d.body.setAttribute("class", "describe-variable");
450         var p;
452         p = g.element("p", d.body);
453         g.variable_reference(this.variable, p);
454         var uvar = user_variables.get(this.variable);
455         if (uvar.source_code_reference)  {
456             g.text(" is a user variable in ", p);
457             g.source_code_reference(uvar.source_code_reference, p);
458             g.text(".", p);
459         } else {
460             g.text(" is a user variable.", p);
461         }
463         p = g.element("p", d.body);
464         g.text("It's value is: ", p);
465         {
466             let s = pretty_print_value(conkeror[this.variable]);
467             let pre = g.element("pre", p);
468             g.text(s, pre);
469         }
471         if (uvar.doc != null)
472             g.help_text(uvar.doc, d.body);
474         p = g.element("p", d.body);
475         g.text("It's default value is: ", p);
476         {
477             let s = pretty_print_value(user_variables.get(this.variable).default_value);
478             let pre = g.element("pre", p);
479             g.text(s, pre);
480         }
481     },
483     __proto__: special_buffer.prototype
487 function describe_variable(buffer, variable, target) {
488     create_buffer(buffer.window,
489                   buffer_creator(describe_variable_buffer,
490                                  $configuration = buffer.configuration,
491                                  $variable = variable),
492                   target);
494 interactive("describe-variable", function (I) {
495     describe_variable(I.buffer, (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
496                      I.browse_target("describe-variable"));
498 default_browse_targets["describe-variable"] = "find-url";