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
7 * Use, modification, and distribution are subject to the terms specified in the
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
);
19 msg
= command
+ " is not on any key";
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:")));
29 function help_document_generator (document
, buffer
) {
30 dom_generator
.call(this, document
, XHTML_NS
);
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");
41 parent
.appendChild(node
);
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
) {
56 yield ref
.open_in_editor();
58 handle_interactive_error(buffer
.window
, e
);
60 event
.preventDefault();
61 event
.stopPropagation();
62 }, false /* capture */);
63 x
.textContent
= (module_name
!= null ? module_name
: ref
.file_name
);
66 parent
.appendChild(f
);
70 command_name: function (name
, parent
) {
71 var node
= this.element("span", "class", "command");
72 this.text(name
, node
);
74 parent
.appendChild(node
);
78 command_reference: function (name
, parent
) {
79 var node
= this.element("a",
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
);
91 parent
.appendChild(node
);
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
);
100 parent
.appendChild(node
);
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
) {
109 if (para
.length
== 0)
112 var p
= this.element("p", f
);
114 var regexp
= /`([a-zA-Z0-9_\-$]+)\'/g;
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
;
125 if (last_index
< para
.length
)
126 this.text(para
.substring(last_index
), p
);
129 parent
.appendChild(f
);
133 add_help_stylesheet: function () {
134 this.add_stylesheet("chrome://conkeror-gui/content/help.css");
139 function help_buffer_modality (buffer
, element
) {
140 buffer
.keymaps
.push(help_buffer_keymap
);
147 define_keywords("$binding_list");
148 function describe_bindings_buffer (window
) {
149 this.constructor_begin();
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 toString: function () "#<describe_bindings_buffer>",
160 title
: "Key bindings",
162 description
: "*bindings*",
164 generate: function () {
165 var d
= this.document
;
166 var list
= this.binding_list
;
167 delete this.binding_list
;
169 var list_by_keymap
= {};
170 var keymap_list
= [];
171 for each (let x
in list
) {
172 let name
= x
.bound_in
|| "";
174 if (name
in list_by_keymap
)
175 km
= list_by_keymap
[name
];
177 km
= list_by_keymap
[name
] = {list_by_category
: {}, category_list
: [], name
: name
};
178 keymap_list
.push(km
);
180 let catname
= x
.category
|| "";
182 if (catname
in km
.list_by_category
)
183 cat
= km
.list_by_category
[catname
];
185 cat
= km
.list_by_category
[catname
] = [];
188 km
.category_list
.unshift(cat
);
190 km
.category_list
.push(cat
);
195 var g
= new help_document_generator(d
, this);
196 g
.add_help_stylesheet();
198 d
.body
.setAttribute("class", "help-list");
200 for each (let km
in keymap_list
) {
201 g
.text(km
.name
, g
.element("h1", d
.body
));
202 for each (let cat
in km
.category_list
) {
204 g
.text(cat
.name
, g
.element("h2", d
.body
));
206 let table
= g
.element("table", d
.body
);
207 for (var i
= 0; i
< cat
.length
; ++i
) {
209 let tr
= g
.element("tr", table
, "class", (i
% 2 == 0) ? "even" : "odd");
210 let seq_td
= g
.element("td", tr
, "class", "key-binding");
211 g
.text(bind
.seq
, seq_td
);
212 let command_td
= g
.element("td", tr
, "class", "command");
214 if (bind
.command
!= null) {
215 if (typeof(bind
.command
) == "function") {
216 g
.text("[function]", command_td
);
218 let cmd
= interactive_commands
[bind
.command
];
220 g
.command_reference(cmd
.name
, command_td
);
221 help_str
= cmd
.shortdoc
;
223 g
.text(bind
.command
, command_td
);
226 } else if (bind
.keymap
!= null) {
227 g
.text("["+bind
.keymap
+"]", command_td
);
228 } else if (bind
.fallthrough
)
229 g
.text("[pass through]", command_td
);
230 let help_td
= g
.element("td", tr
, "class", "help");
231 g
.text(help_str
|| "", help_td
);
237 __proto__
: special_buffer
.prototype
241 function describe_bindings (buffer
, target
, keymaps
, prefix
) {
244 keymaps
= get_current_keymaps(buffer
.window
);
246 prefix
= format_binding_sequence(
247 prefix
.map(function (x
) { return {key
:x
}; }))+" ";
250 for_each_key_binding(keymaps
, function (binding_stack
) {
251 var last
= binding_stack
[binding_stack
.length
- 1];
252 //we don't care about auto-generated keymap bindings.
253 if (last
.keymap
&& last
.keymap
.anonymous
)
257 for (let i
= binding_stack
.length
- 1; i
>= 0; --i
) {
258 bound_in
= binding_stack
[i
].bound_in
;
260 if (bound_in
.name
&& !bound_in
.anonymous
)
262 bound_in
= bound_in
.bound_in
;
266 if (last
.keymap
&& ! last
.keymap
.anonymous
)
267 keymap
= last
.keymap
.name
;
268 var bind
= {seq
: prefix
+format_binding_sequence(binding_stack
),
269 fallthrough
: last
.fallthrough
,
270 command
: last
.command
,
272 bound_in
: bound_in
.name
,
273 category
: last
.category
277 create_buffer(buffer
.window
, buffer_creator(describe_bindings_buffer
,
279 $binding_list
= list
),
282 function describe_bindings_new_buffer (I
) {
283 describe_bindings(I
.buffer
, OPEN_NEW_BUFFER
);
285 function describe_bindings_new_window (I
) {
286 describe_bindings(I
.buffer
, OPEN_NEW_WINDOW
);
288 interactive("describe-bindings",
289 "Show a help buffer describing the bindings in the context keymaps, "+
290 "meaning the top-level keymaps according to the focus context in the "+
292 alternates(describe_bindings_new_buffer
,
293 describe_bindings_new_window
));
295 function describe_active_bindings_new_buffer (I
) {
296 describe_bindings(I
.buffer
, OPEN_NEW_BUFFER
,
297 I
.keymaps
|| get_current_keymaps(I
.buffer
.window
),
298 I
.key_sequence
.slice(0, -1));
300 function describe_active_bindings_new_window (I
) {
301 describe_bindings(I
.buffer
, OPEN_NEW_WINDOW
,
302 I
.keymaps
|| get_current_keymaps(I
.buffer
.window
),
303 I
.key_sequence
.slice(0, -1));
305 interactive("describe-active-bindings",
306 "Show a help buffer describing the bindings in the active keymaps, "+
307 "meaning the keymaps in the middle of an ongoing key sequence. This "+
308 "command is intended to be called via `sequence_help_keymap'. For "+
309 "that reason, `describe-active-bindings' does not consume and prefix "+
310 "commands like `universal-argument', as doing so would lead to "+
311 "ambiguities with respect to the intent of the user.",
312 describe_active_bindings_new_buffer
);
319 define_keywords("$command_list");
320 function apropos_command_buffer (window
) {
321 this.constructor_begin();
323 special_buffer
.call(this, window
, forward_keywords(arguments
));
324 this.command_list
= arguments
.$command_list
;
325 this.modalities
.push(help_buffer_modality
);
326 this.constructor_end();
328 apropos_command_buffer
.prototype = {
329 constructor: apropos_command_buffer
,
330 toString: function () "#<apropos_command_buffer>",
332 title
: "Apropos commands",
334 description
: "*Apropos*",
336 generate: function () {
337 var d
= this.document
;
338 var list
= this.command_list
;
339 delete this.command_list
;
341 var g
= new help_document_generator(d
, this);
342 g
.add_help_stylesheet();
344 d
.body
.setAttribute("class", "help-list");
346 var table
= d
.createElementNS(XHTML_NS
, "table");
347 for (var i
= 0; i
< list
.length
; ++i
) {
348 var binding
= list
[i
];
349 var tr
= d
.createElementNS(XHTML_NS
, "tr");
350 tr
.setAttribute("class", (i
% 2 == 0) ? "even" : "odd");
352 var command_td
= d
.createElementNS(XHTML_NS
,"td");
353 g
.command_reference(binding
.name
, command_td
);
356 if (binding
.cmd
.shortdoc
!= null)
357 shortdoc
= binding
.cmd
.shortdoc
;
358 tr
.appendChild(command_td
);
360 var shortdoc_td
= d
.createElementNS(XHTML_NS
, "td");
361 shortdoc_td
.setAttribute("class", "help");
362 shortdoc_td
.textContent
= shortdoc
;
363 tr
.appendChild(shortdoc_td
);
365 table
.appendChild(tr
);
367 d
.body
.appendChild(table
);
370 __proto__
: special_buffer
.prototype
374 /* TODO: support regexps/etc. */
375 function apropos_command (buffer, substring, target) {
377 for (let [name, cmd] in Iterator(interactive_commands)) {
378 if (name.indexOf(substring) != -1) {
379 var binding = {name: name, cmd: cmd};
383 list.sort(function (a,b) {
390 create_buffer(buffer.window, buffer_creator(apropos_command_buffer,
392 $command_list = list),
396 function apropos_command_new_buffer (I) {
397 apropos_command(I.buffer,
398 (yield I.minibuffer.read($prompt = "Apropos command:",
399 $history = "apropos")),
402 function apropos_command_new_window (I) {
403 apropos_command(I.buffer,
404 (yield I.minibuffer.read($prompt = "Apropos command:",
405 $history = "apropos")),
408 interactive("apropos-command", "List commands whose names contain a given substring.",
409 alternates(apropos_command_new_buffer, apropos_command_new_window));
417 define_keywords("$command", "$bindings");
418 function describe_command_buffer (window
) {
419 this.constructor_begin();
421 special_buffer
.call(this, window
, forward_keywords(arguments
));
422 this.bindings
= arguments
.$bindings
;
423 this.command
= arguments
.$command
;
424 this.cmd
= interactive_commands
[this.command
];
425 this.source_code_reference
= this.cmd
.source_code_reference
;
426 this.modalities
.push(help_buffer_modality
);
427 this.constructor_end();
429 describe_command_buffer
.prototype = {
430 constructor: describe_command_buffer
,
431 toString: function () "#<describe_command_buffer>",
433 get title () { return "Command help: " + this.command
; },
435 description
: "*help*",
437 generate: function () {
438 var d
= this.document
;
440 var g
= new help_document_generator(d
, this);
442 g
.add_help_stylesheet();
443 d
.body
.setAttribute("class", "describe-command");
447 p
= g
.element("p", d
.body
);
448 g
.command_reference(this.command
, p
);
449 var cmd
= interactive_commands
[this.command
];
450 if (cmd
.source_code_reference
) {
451 g
.text(" is an interactive command in ", p
);
452 g
.source_code_reference(cmd
.source_code_reference
, p
);
455 g
.text(" is an interactive command.", p
);
458 if (this.bindings
.length
> 0) {
459 p
= g
.element("p", d
.body
);
460 g
.text("It is bound to ", p
);
461 for (var i
= 0; i
< this.bindings
.length
; ++i
) {
464 g
.key_binding(this.bindings
[i
], p
);
470 g
.help_text(cmd
.doc
, d
.body
);
473 __proto__
: special_buffer
.prototype
477 function describe_command (buffer
, command
, target
) {
478 var keymaps
= get_current_keymaps(buffer
.window
);
479 var bindings
= keymap_lookup_command(keymaps
, command
);
480 create_buffer(buffer
.window
,
481 buffer_creator(describe_command_buffer
,
484 $bindings
= bindings
),
487 function describe_command_new_buffer (I
) {
488 describe_command(I
.buffer
, (yield I
.minibuffer
.read_command($prompt
= "Describe command:")),
491 function describe_command_new_window (I
) {
492 describe_command(I
.buffer
, (yield I
.minibuffer
.read_command($prompt
= "Describe command:")),
495 interactive("describe-command", null,
496 alternates(describe_command_new_buffer
, describe_command_new_window
));
500 function view_referenced_source_code (buffer
) {
501 if (buffer
.source_code_reference
== null)
502 throw interactive_error("Command not valid in current buffer.");
503 yield buffer
.source_code_reference
.open_in_editor();
505 interactive("view-referenced-source-code", null,
506 function (I
) {yield view_referenced_source_code(I
.buffer
);});
513 define_keywords("$binding", "$other_bindings", "$key_sequence");
514 function describe_key_buffer (window
) {
515 this.constructor_begin();
517 special_buffer
.call(this, window
, forward_keywords(arguments
));
518 this.key_sequence
= arguments
.$key_sequence
;
519 this.bindings
= arguments
.$other_bindings
;
520 this.bind
= arguments
.$binding
;
521 this.source_code_reference
= this.bind
.source_code_reference
;
522 this.modalities
.push(help_buffer_modality
);
523 this.constructor_end();
525 describe_key_buffer
.prototype = {
526 constructor: describe_key_buffer
,
527 toString: function () "#<describe_key_buffer>",
529 get title () { return "Key help: " + this.key_sequence
; },
531 description
: "*help*",
533 generate: function () {
534 var d
= this.document
;
536 var g
= new help_document_generator(d
, this);
538 g
.add_help_stylesheet();
539 d
.body
.setAttribute("class", "describe-key");
543 p
= g
.element("p", d
.body
);
544 g
.key_binding(this.key_sequence
, p
);
545 g
.text(" is bound to the command ", p
);
546 var command
= this.bind
.command
;
548 g
.command_name("[pass through]", p
);
550 g
.command_reference(command
, p
);
551 if (this.bind
.browser_object
!= null) {
552 g
.text(" with the browser object, ", p
);
553 if (this.bind
.browser_object
instanceof Function
) {
554 g
.text("<anonymous browser-object function>", p
);
555 } else if (this.bind
.browser_object
instanceof browser_object_class
) {
556 g
.text(this.bind
.browser_object
.name
, p
);
557 } else if (typeof(this.bind
.browser_object
) == "string") {
558 g
.text('"'+this.bind
.browser_object
+'"', p
);
560 g
.text(this.bind
.browser_object
, p
);
563 if (this.source_code_reference
) {
565 g
.source_code_reference(this.source_code_reference
, p
);
569 if (command
!= null) {
570 p
= g
.element("p", d
.body
);
571 g
.command_reference(command
, p
);
572 var cmd
= interactive_commands
[command
];
573 if (cmd
.source_code_reference
) {
574 g
.text(" is an interactive command in ", p
);
575 g
.source_code_reference(cmd
.source_code_reference
, p
);
578 g
.text(" is an interactive command.", p
);
581 if (this.bindings
.length
> 0) {
582 p
= g
.element("p", d
.body
);
583 g
.text("It is bound to ", p
);
584 for (var i
= 0; i
< this.bindings
.length
; ++i
) {
587 g
.key_binding(this.bindings
[i
], p
);
593 g
.help_text(cmd
.doc
, d
.body
);
597 __proto__
: special_buffer
.prototype
601 function describe_key (buffer
, key_info
, target
) {
603 var seq
= key_info
[0];
604 var bind
= key_info
[1];
605 var keymaps
= get_current_keymaps(buffer
.window
);
607 bindings
= keymap_lookup_command(keymaps
, bind
.command
);
610 create_buffer(buffer
.window
,
611 buffer_creator(describe_key_buffer
,
613 $key_sequence
= seq
.join(" "),
614 $other_bindings
= bindings
,
618 function describe_key_new_buffer (I
) {
619 describe_key(I
.buffer
,
620 (yield I
.minibuffer
.read_key_binding($prompt
= "Describe key:")),
623 function describe_key_new_window (I
) {
624 describe_key(I
.buffer
,
625 (yield I
.minibuffer
.read_key_binding($prompt
= "Describe key:")),
629 function describe_key_briefly (buffer
, key_info
) {
631 var seq
= key_info
[0];
632 var bind
= key_info
[1];
633 var browser_object
= "";
634 if (bind
.browser_object
!= null) {
635 browser_object
+= " on the browser object, ";
636 if (bind
.browser_object
instanceof Function
) {
637 browser_object
+= "<anonymous browser-object function>";
638 } else if (bind
.browser_object
instanceof browser_object_class
) {
639 browser_object
+= bind
.browser_object
.name
;
640 } else if (typeof(bind
.browser_object
) == "string") {
641 browser_object
+= '"'+bind
.browser_object
+'"';
643 browser_object
+= bind
.browser_object
;
646 buffer
.window
.minibuffer
.message(seq
.join(" ") + " runs the command " + bind
.command
+ browser_object
);
649 interactive("describe-key", null,
650 alternates(describe_key_new_buffer
, describe_key_new_window
));
652 interactive("describe-key-briefly", null,
654 describe_key_briefly(
656 (yield I
.minibuffer
.read_key_binding($prompt
= "Describe key:")));
665 define_keywords("$variable");
666 function describe_variable_buffer (window
) {
667 this.constructor_begin();
669 special_buffer
.call(this, window
, forward_keywords(arguments
));
670 this.variable
= arguments
.$variable
;
671 this.cmd
= user_variables
[this.variable
];
672 this.source_code_reference
= this.cmd
.source_code_reference
;
673 this.modalities
.push(help_buffer_modality
);
674 this.constructor_end();
676 describe_variable_buffer
.prototype = {
677 constructor: describe_variable_buffer
,
678 toString: function () "#<describe_variable_buffer>",
680 get title () { return "Variable help: " + this.variable
; },
682 description
: "*help*",
684 generate: function () {
685 var d
= this.document
;
687 var g
= new help_document_generator(d
, this);
689 g
.add_help_stylesheet();
690 d
.body
.setAttribute("class", "describe-variable");
694 p
= g
.element("p", d
.body
);
695 g
.variable_reference(this.variable
, p
);
696 var uvar
= user_variables
[this.variable
];
697 if (uvar
.source_code_reference
) {
698 g
.text(" is a user variable in ", p
);
699 g
.source_code_reference(uvar
.source_code_reference
, p
);
702 g
.text(" is a user variable.", p
);
705 p
= g
.element("p", d
.body
);
706 g
.text("Its value is: ", p
);
707 let value
= conkeror
[this.variable
];
709 let s
= pretty_print_value(value
);
710 let pre
= g
.element("pre", p
);
714 if (uvar
.doc
!= null)
715 g
.help_text(uvar
.doc
, d
.body
);
717 if (uvar
.default_value
!== undefined &&
718 (uvar
.default_value
!== value
||
719 (typeof(uvar
.default_value
) != "object"))) {
720 p
= g
.element("p", d
.body
);
721 g
.text("Its default value is: ", p
);
723 let s
= pretty_print_value(uvar
.default_value
);
724 let pre
= g
.element("pre", p
);
730 __proto__
: special_buffer
.prototype
734 function describe_variable (buffer
, variable
, target
) {
735 create_buffer(buffer
.window
,
736 buffer_creator(describe_variable_buffer
,
738 $variable
= variable
),
741 function describe_variable_new_buffer (I
) {
742 describe_variable(I
.buffer
,
743 (yield I
.minibuffer
.read_user_variable($prompt
= "Describe variable:")),
746 function describe_variable_new_window (I
) {
747 describe_variable(I
.buffer
,
748 (yield I
.minibuffer
.read_user_variable($prompt
= "Describe variable:")),
751 interactive("describe-variable", null,
752 alternates(describe_variable_new_buffer
, describe_variable_new_window
));
757 * Describe Preference
760 function describe_preference (buffer
, preference
, target
) {
761 let key
= preference
.charAt(0).toUpperCase() + preference
.substring(1);
762 let url
= "http://kb.mozillazine.org/" + key
;
763 browser_object_follow(buffer
, target
, url
);
765 function describe_preference_new_buffer (I
) {
766 describe_preference(I
.buffer
, (yield I
.minibuffer
.read_preference($prompt
= "Describe preference:")),
769 function describe_preference_new_window (I
) {
770 describe_preference(I
.buffer
, (yield I
.minibuffer
.read_preference($prompt
= "Describe preference:")),
773 interactive("describe-preference", null,
774 alternates(describe_preference_new_buffer
, describe_preference_new_window
));