Add generic label mechanism
[conkeror.git] / modules / help.js
blob574deac5a0e581159b12f2f99846beb097ee69d0
1 /**
2 * (C) Copyright 2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2008 Nelson Elhage
4 * (C) Copyright 2008 David Glasser
6 * Use, modification, and distribution are subject to the terms specified in the
7 * COPYING file.
8 **/
10 require("special-buffer.js");
11 require("interactive.js");
13 function help_buffer(window, element) {
14 keywords(arguments);
15 conkeror.buffer.call(this, window, element, forward_keywords(arguments));
18 help_buffer.prototype = {
19 constructor: help_buffer,
21 __proto__: special_buffer.prototype
24 function where_is_command(buffer, command) {
25 var list = find_command_in_keymap(buffer, command);
26 var msg;
27 if (list.length == 0)
28 msg = command + " is not on any key";
29 else
30 msg = command + " is on " + list.join(", ");
31 buffer.window.minibuffer.message(msg);
33 interactive("where-is", null, function (I) {
34 where_is_command(I.buffer,
35 (yield I.minibuffer.read_command($prompt = "Where is command:")));
36 });
38 function help_document_generator(document, buffer) {
39 dom_generator.call(this, document, XHTML_NS);
40 this.buffer = buffer;
42 help_document_generator.prototype = {
43 __proto__: dom_generator.prototype,
45 key_binding : function(str, parent) {
46 var node = this.element("span", "class", "key-binding");
47 this.text(str, node);
48 if (parent)
49 parent.appendChild(node);
50 return node;
53 source_code_reference : function(ref, parent) {
54 var f = this.document.createDocumentFragment();
55 var module_name = ref.module_name;
56 //f.appendChild(this.text(module_name != null ? "module " : "file "));
57 var x = this.element("a",
58 "class", "source-code-reference",
59 "href", "javascript:");
60 x.addEventListener("click", function (event) {
61 co_call(ref.open_in_editor());
62 event.preventDefault();
63 event.stopPropagation();
64 }, false /* capture */, false /* allow untrusted */);
65 x.textContent = (module_name != null ? module_name : ref.file_name);
66 f.appendChild(x);
67 if (parent)
68 parent.appendChild(f);
69 return f;
72 command_name : function(name, parent) {
73 var node = this.element("span", "class", "command");
74 this.text(name, node);
75 if (parent)
76 parent.appendChild(node);
77 return node;
80 command_reference : function(name, parent) {
81 var node = this.element("a",
82 "class", "command",
83 "href", "javascript:");
84 var buffer = this.buffer;
85 node.addEventListener("click", function (event) {
86 /* FIXME: don't hardcode browse target */
87 describe_command(buffer, name, OPEN_NEW_BUFFER);
88 event.preventDefault();
89 event.stopPropagation();
90 }, false /* capture */, false /* allow untrusted */);
91 this.text(name, node);
92 if (parent)
93 parent.appendChild(node);
94 return node;
97 variable_reference : function(name, parent) {
98 var node = this.element("a", "class", "variable", "href", "#");
99 /* FIXME: make this work */
100 this.text(name, node);
101 if (parent)
102 parent.appendChild(node);
103 return node;
106 help_text : function(str, parent) {
107 var paras = str.split("\n");
108 var f = this.document.createDocumentFragment();
109 for (var i = 0; i < paras.length; ++i) {
110 var para = paras[i];
111 if (para.length == 0)
112 continue;
114 var p = this.element("p", f);
116 var regexp = /`([a-zA-Z0-9_\-$]+)\'/g;
118 var match;
119 var last_index = 0;
120 while ((match = regexp.exec(para)) != null) {
121 this.text(para.substring(last_index, match.index), p);
122 var command = match[1];
123 /* FIXME: check if it is a valid command */
124 this.command_reference(command, p);
125 last_index = regexp.lastIndex;
127 if (last_index < para.length)
128 this.text(para.substring(last_index), p);
130 if (parent != null)
131 parent.appendChild(f);
132 return f;
135 add_help_stylesheet : function () {
136 this.add_stylesheet("chrome://conkeror/content/help.css");
140 define_keywords("$binding_list");
141 function describe_bindings_buffer(window, element) {
142 this.constructor_begin();
143 keywords(arguments);
144 special_buffer.call(this, window, element, forward_keywords(arguments));
145 this.binding_list = arguments.$binding_list;
146 this.constructor_end();
149 describe_bindings_buffer.prototype = {
151 get keymap() {
152 return help_buffer_keymap;
155 title : "Key bindings",
157 description : "*bindings*",
159 generate : function () {
160 var d = this.document;
161 var list = this.binding_list;
162 delete this.binding_list;
164 var list_by_keymap = {};
165 var keymap_list = [];
166 for each (let x in list) {
167 let name = x.bound_in || "";
168 let km;
169 if (name in list_by_keymap)
170 km = list_by_keymap[name];
171 else {
172 km = list_by_keymap[name] = {list_by_category: {}, category_list: [], name: name};
173 keymap_list.push(km);
175 let catname = x.category || "";
176 let cat;
177 if (catname in km.list_by_category)
178 cat = km.list_by_category[catname];
179 else {
180 cat = km.list_by_category[catname] = [];
181 cat.name = catname;
182 if (catname == "")
183 km.category_list.unshift(cat);
184 else
185 km.category_list.push(cat);
187 cat.push(x);
190 var g = new help_document_generator(d, this);
191 g.add_help_stylesheet();
193 d.body.setAttribute("class", "help-list");
195 for each (let km in keymap_list) {
196 g.text(km.name, g.element("h1", d.body));
197 for each (let cat in km.category_list) {
198 if (cat.name != "")
199 g.text(cat.name, g.element("h2", d.body));
201 let table = g.element("table", d.body);
202 for (var i = 0; i < cat.length; ++i) {
203 let bind = cat[i];
204 let tr = g.element("tr", table, "class", (i % 2 == 0) ? "even" : "odd");
205 let seq_td = g.element("td", tr, "class", "key-binding");
206 g.text(bind.seq, seq_td);
207 let command_td = g.element("td", tr, "class", "command");
208 let help_str = null;
209 if (bind.command != null) {
210 if (typeof(bind.command) == "function") {
211 g.text("[function]", command_td);
212 } else {
213 g.text(bind.command, command_td);
214 let cmd = interactive_commands.get(bind.command);
215 if (cmd != null)
216 help_str = cmd.shortdoc;
219 else if (bind.fallthrough)
220 g.text("[pass through]", command_td);
221 let help_td = g.element("td", tr, "class", "help");
222 g.text(help_str || "", help_td);
228 __proto__: special_buffer.prototype
232 function describe_bindings(buffer, target) {
233 var list = [];
234 for_each_key_binding(buffer, function (binding_stack) {
235 var last = binding_stack[binding_stack.length - 1];
236 if (last.command == null && !last.fallthrough)
237 return;
238 let bound_in = null;
239 outer:
240 for (let i = binding_stack.length - 1; i >= 0; --i) {
241 bound_in = binding_stack[i].bound_in;
242 while (bound_in) {
243 if (bound_in.name)
244 break outer;
245 bound_in = bound_in.bound_in;
248 var bind = {seq: format_binding_sequence(binding_stack),
249 fallthrough: last.fallthrough,
250 command: last.command,
251 bound_in: bound_in.name,
252 category: last.category
254 list.push(bind);
256 create_buffer(buffer.window, buffer_creator(describe_bindings_buffer,
257 $configuration = buffer.configuration,
258 $binding_list = list),
259 target);
261 interactive("describe-bindings", null, function (I) {describe_bindings(I.buffer, I.browse_target("describe-bindings"));});
262 default_browse_targets["describe-bindings"] = "find-url-new-buffer";
265 define_keywords("$command_list");
266 function apropos_command_buffer(window, element) {
267 this.constructor_begin();
268 keywords(arguments);
269 special_buffer.call(this, window, element, forward_keywords(arguments));
270 this.command_list = arguments.$command_list;
271 this.constructor_end();
274 apropos_command_buffer.prototype = {
276 get keymap() {
277 return help_buffer_keymap;
280 title : "Apropos commands",
282 description : "*Apropos*",
284 generate : function () {
285 var d = this.document;
286 var list = this.command_list;
287 delete this.command_list;
289 var g = new help_document_generator(d, this);
290 g.add_help_stylesheet();
292 d.body.setAttribute("class", "help-list");
294 var table = d.createElementNS(XHTML_NS, "table");
295 for (var i = 0; i < list.length; ++i) {
296 var binding = list[i];
297 var tr = d.createElementNS(XHTML_NS, "tr");
298 tr.setAttribute("class", (i % 2 == 0) ? "even" : "odd");
300 var command_td = d.createElementNS(XHTML_NS,"td");
301 g.command_reference(binding.name, command_td);
303 var shortdoc = "";
304 if (binding.cmd.shortdoc != null)
305 shortdoc = binding.cmd.shortdoc;
306 tr.appendChild(command_td);
308 var shortdoc_td = d.createElementNS(XHTML_NS, "td");
309 shortdoc_td.setAttribute("class", "help");
310 shortdoc_td.textContent = shortdoc;
311 tr.appendChild(shortdoc_td);
313 table.appendChild(tr);
315 d.body.appendChild(table);
318 __proto__: special_buffer.prototype
322 /* TODO: support regexps/etc. */
323 function apropos_command(buffer, substring, target) {
324 var list = [];
325 interactive_commands.for_each(function (name, cmd) {
326 if (name.indexOf(substring) != -1) {
327 var binding = {name: name, cmd: cmd};
328 list.push(binding);
331 list.sort(function (a,b) {
332 if (a.name < b.name)
333 return -1;
334 if (a.name > b.name)
335 return 1;
336 return 0
338 create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
339 $configuration = buffer.configuration,
340 $command_list = list),
341 target);
344 interactive("apropos-command", "List commands whose names contain a given substring.",
345 function (I) {
346 apropos_command(I.buffer,
347 (yield I.minibuffer.read($prompt = "Apropos command:",
348 $history = "apropos")),
349 I.browse_target("apropos-command"));
351 default_browse_targets["apropos-command"] = "find-url-new-buffer";
355 define_keywords("$command", "$bindings");
356 function describe_command_buffer(window, element) {
357 this.constructor_begin();
358 keywords(arguments);
359 special_buffer.call(this, window, element, forward_keywords(arguments));
360 this.bindings = arguments.$bindings;
361 this.command = arguments.$command;
362 this.cmd = interactive_commands.get(this.command);
363 this.source_code_reference = this.cmd.source_code_reference;
364 this.constructor_end();
367 describe_command_buffer.prototype = {
369 get keymap() {
370 return help_buffer_keymap;
373 get title() { return "Command help: " + this.command; },
375 description : "*help*",
377 generate : function () {
378 var d = this.document;
380 var g = new help_document_generator(d, this);
382 g.add_help_stylesheet();
383 d.body.setAttribute("class", "describe-command");
385 var p;
387 p = g.element("p", d.body);
388 g.command_reference(this.command, p);
389 var cmd = interactive_commands.get(this.command);
390 if (cmd.source_code_reference) {
391 g.text(" is an interactive command in ", p);
392 g.source_code_reference(cmd.source_code_reference, p);
393 g.text(".", p);
394 } else {
395 g.text(" is an interactive command.", p);
398 if (this.bindings.length > 0) {
399 p = g.element("p", d.body);
400 g.text("It is bound to ", p);
401 for (var i = 0; i < this.bindings.length; ++i) {
402 if (i != 0)
403 g.text(", ", p);
404 g.key_binding(this.bindings[i], p);
406 g.text(".", p);
409 if (cmd.doc != null)
410 g.help_text(cmd.doc, d.body);
413 __proto__: special_buffer.prototype
417 function describe_command(buffer, command, target) {
418 var bindings = find_command_in_keymap(buffer, command);
419 create_buffer(buffer.window,
420 buffer_creator(describe_command_buffer,
421 $configuration = buffer.configuration,
422 $command = command,
423 $bindings = bindings),
424 target);
426 interactive("describe-command", null, function (I) {
427 describe_command(I.buffer, (yield I.minibuffer.read_command($prompt = "Describe command:")),
428 I.browse_target("describe-command"));
430 default_browse_targets["describe-command"] = "find-url-new-buffer";
437 function view_referenced_source_code(buffer) {
438 if (buffer.source_code_reference == null)
439 throw interactive_error("Command not valid in current buffer.");
440 yield buffer.source_code_reference.open_in_editor();
442 interactive("view-referenced-source-code", null, function (I) {yield view_referenced_source_code(I.buffer);});
445 define_keywords("$binding", "$other_bindings", "$key_sequence");
446 function describe_key_buffer(window, element) {
447 this.constructor_begin();
448 keywords(arguments);
449 special_buffer.call(this, window, element, forward_keywords(arguments));
450 this.key_sequence = arguments.$key_sequence;
451 this.bindings = arguments.$other_bindings;
452 this.bind = arguments.$binding;
453 this.source_code_reference = this.bind.source_code_reference;
454 this.constructor_end();
457 describe_key_buffer.prototype = {
459 get keymap() {
460 return help_buffer_keymap;
463 get title() { return "Key help: " + this.key_sequence; },
465 description : "*help*",
467 generate : function () {
468 var d = this.document;
470 var g = new help_document_generator(d, this);
472 g.add_help_stylesheet();
473 d.body.setAttribute("class", "describe-key");
475 var p;
477 p = g.element("p", d.body);
478 g.key_binding(this.key_sequence, p);
479 g.text(" is bound to the command ", p);
480 var command = this.bind.command;
481 if (command == null)
482 g.command_name("[pass through]", p);
483 else
484 g.command_reference(command, p);
485 if (this.source_code_reference) {
486 g.text(" in ", p);
487 g.source_code_reference(this.source_code_reference, p);
489 g.text(".", p);
491 if (command != null) {
492 p = g.element("p", d.body);
493 g.command_reference(command, p);
494 var cmd = interactive_commands.get(command);
495 if (cmd.source_code_reference) {
496 g.text(" is an interactive command in ", p);
497 g.source_code_reference(cmd.source_code_reference, p);
498 g.text(".", p);
499 } else {
500 g.text(" is an interactive command.", p);
503 if (this.bindings.length > 0) {
504 p = g.element("p", d.body);
505 g.text("It is bound to ", p);
506 for (var i = 0; i < this.bindings.length; ++i) {
507 if (i != 0)
508 g.text(", ", p);
509 g.key_binding(this.bindings[i], p);
511 g.text(".", p);
514 if (cmd.doc != null)
515 g.help_text(cmd.doc, d.body);
519 __proto__: special_buffer.prototype
523 function describe_key(buffer, key_info, target) {
524 var bindings;
525 var seq = key_info[0];
526 var bind = key_info[1];
528 if (bind.command)
529 bindings = find_command_in_keymap(buffer, bind.command);
530 else
531 bindings = [];
533 create_buffer(buffer.window,
534 buffer_creator(describe_key_buffer,
535 $configuration = buffer.configuration,
536 $key_sequence = seq.join(" "),
537 $other_bindings = bindings,
538 $binding = bind),
539 target);
542 function describe_key_briefly(buffer, key_info) {
543 var bindings;
544 var seq = key_info[0];
545 var bind = key_info[1];
547 buffer.window.minibuffer.message(seq.join(" ") + " runs the command " + bind.command);
550 interactive("describe-key", null, function (I) {
551 describe_key(I.buffer,
552 (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)),
553 I.browse_target("describe-key"));
555 interactive("describe-key-briefly", null, function (I) {
556 describe_key_briefly(I.buffer,
557 (yield I.minibuffer.read_key_binding($prompt = "Describe key:", $buffer = I.buffer)));
559 default_browse_targets["describe-key"] = "find-url-new-buffer";
564 define_keywords("$variable");
565 function describe_variable_buffer(window, element) {
566 this.constructor_begin();
567 keywords(arguments);
568 special_buffer.call(this, window, element, forward_keywords(arguments));
569 this.variable = arguments.$variable;
570 this.cmd = user_variables.get(this.variable);
571 this.source_code_reference = this.cmd.source_code_reference;
572 this.constructor_end();
575 function pretty_print_value(value) {
576 if (value === undefined)
577 return "undefined";
578 if (value === null)
579 return "null";
580 if (typeof(value) == "object")
581 return value.toSource();
582 if (typeof(value) == "function")
583 return value.toString();
584 if (typeof(value) == "string") {
585 let s = value.toSource();
586 // toSource returns: (new String("<blah>"))
587 // we want just: "<blah>"
588 return s.substring(12, s.length - 2);
590 return new String(value);
593 describe_variable_buffer.prototype = {
595 get keymap() {
596 return help_buffer_keymap;
599 get title() { return "Variable help: " + this.variable; },
601 description : "*help*",
603 generate : function () {
604 var d = this.document;
606 var g = new help_document_generator(d, this);
608 g.add_help_stylesheet();
609 d.body.setAttribute("class", "describe-variable");
611 var p;
613 p = g.element("p", d.body);
614 g.variable_reference(this.variable, p);
615 var uvar = user_variables.get(this.variable);
616 if (uvar.source_code_reference) {
617 g.text(" is a user variable in ", p);
618 g.source_code_reference(uvar.source_code_reference, p);
619 g.text(".", p);
620 } else {
621 g.text(" is a user variable.", p);
624 p = g.element("p", d.body);
625 g.text("Its value is: ", p);
627 let s = pretty_print_value(conkeror[this.variable]);
628 let pre = g.element("pre", p);
629 g.text(s, pre);
632 if (uvar.doc != null)
633 g.help_text(uvar.doc, d.body);
635 if (uvar.default_value !== undefined) {
636 p = g.element("p", d.body);
637 g.text("Its default value is: ", p);
639 let s = pretty_print_value(uvar.default_value);
640 let pre = g.element("pre", p);
641 g.text(s, pre);
646 __proto__: special_buffer.prototype
650 function describe_variable(buffer, variable, target) {
651 create_buffer(buffer.window,
652 buffer_creator(describe_variable_buffer,
653 $configuration = buffer.configuration,
654 $variable = variable),
655 target);
657 interactive("describe-variable", null, function (I) {
658 describe_variable(I.buffer, (yield I.minibuffer.read_user_variable($prompt = "Describe variable:")),
659 I.browse_target("describe-variable"));
661 default_browse_targets["describe-variable"] = "find-url-new-buffer";
663 function describe_preference(buffer, preference, target) {
664 let key = preference.charAt(0).toUpperCase() + preference.substring(1);
665 let url = "http://kb.mozillazine.org/" + key;
666 browser_element_follow(buffer, target, url);
668 interactive("describe-preference", null, function (I) {
669 describe_preference(I.buffer, (yield I.minibuffer.read_preference($prompt = "Describe preference:")),
670 I.browse_target("describe-preference"));
672 default_browse_targets["describe-preference"] = "find-url-new-buffer";