browser-next-form-field: skip non-visible elements
[conkeror.git] / modules / commands.js
blob49e6a115c49d2d4d032787427c85a53c4f9e5783
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2008 John J. Foerch
4  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5  *
6  * Use, modification, and distribution are subject to the terms specified in the
7  * COPYING file.
8 **/
10 require("content-buffer.js");
12 define_hook("quit_hook");
14 function quit ()
16     quit_hook.run();
17     var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
18         .getService(Ci.nsIAppStartup);
19     appStartup.quit(appStartup.eAttemptQuit);
21 interactive("quit",
22             "Quit Conkeror",
23             quit);
26 function show_conkeror_version (window)
28     window.minibuffer.message (conkeror.version);
30 interactive ("conkeror-version",
31              "Show version information for Conkeror.",
32              function (I) {show_conkeror_version(I.window);});
34 /* FIXME: maybe this should be supported for non-browser buffers */
35 function scroll_horiz_complete (buffer, n)
37     var w = buffer.focused_frame;
38     w.scrollTo (n > 0 ? w.scrollMaxX : 0, w.scrollY);
40 interactive("scroll-beginning-of-line",
41             "Scroll the current frame all the way to the left.",
42             function (I) {scroll_horiz_complete(I.buffer, -1);});
44 interactive("scroll-end-of-line",
45             "Scroll the current frame all the way to the right.",
46             function (I) {scroll_horiz_complete(I.buffer, 1);});
48 function delete_window (window)
50     window.window.close();
52 interactive("delete-window",
53             "Delete the current window.",
54             function (I) {delete_window(I.window);});
56 interactive("jsconsole",
57             "Open the JavaScript console.",
58             "find-url-new-buffer",
59             $browser_object = "chrome://global/content/console.xul");
61 /**
62  * Given a callback func and an interactive context I, call func, passing either
63  * a focused field, or the minibuffer's input element if the minibuffer is
64  * active. Afterward, call `ensure_index_is_visible' on the field. See
65  * `paste_x_primary_selection' and `open_line' for examples.
66  */
67 function call_on_focused_field(I, func) {
68   var m = I.window.minibuffer;
69   var s = m.current_state;
70   if (m._input_mode_enabled) {
71     m._restore_normal_state();
72     var e = m.input_element;
73   } else var e = I.buffer.focused_element;
74   func(e);
75   ensure_index_is_visible (I.window, e, e.selectionStart);
76   if (s && s.handle_input) s.handle_input(m);
79 /**
80  * Replace the current region with modifier(selection). Deactivates region and
81  * sets point to the end of the inserted text, unless keep_point is true, in
82  * which case the point will be left at the beginning of the inserted text.
83  */
84 function modify_region(field, modifier, keep_point) {
85   var replacement =
86     modifier(field.value.substring(field.selectionStart, field.selectionEnd+1));
87   var point = field.selectionStart;
88   field.value =
89     field.value.substr(0, field.selectionStart) + replacement +
90     field.value.substr(field.selectionEnd);
91   if (!keep_point) point += replacement.length;
92   field.setSelectionRange(point, point);
95 function paste_x_primary_selection (field) {
96   modify_region(field, function(str) read_from_x_primary_selection());
98 interactive (
99   "paste-x-primary-selection",
100   "Insert the contents of the X primary selection into the selected field or " +
101   "minibuffer. Deactivates the region if it is active, and leaves the point " +
102   "after the inserted text.",
103   function (I) call_on_focused_field(I, paste_x_primary_selection)
106 function open_line(field) {
107   modify_region(field, function() "\n", true);
110 interactive(
111   "open-line",
112   "If there is an active region, replace is with a newline, otherwise just " +
113   "insert a newline. In both cases leave point before the inserted newline.",
114   function (I) call_on_focused_field(I, open_line)
117 function transpose_chars(field) {
118     var value = field.value;
119     var caret = field.selectionStart; // Caret position.
120     var length = value.length;
122     // If we have less than two character in the field or if we are at the
123     // beginning of the field, do nothing.
124     if (length <= 2 || caret == 0)
125         return;
127     // If we are at the end of the field, switch places on the two last
128     // characters. TODO: This should happen at the end of every line, not only
129     // at the end of the field.
130     if (caret == length)
131         caret--;
133     // Do the transposing.
134     field.value = switch_subarrays(value, caret - 1, caret, caret, caret + 1);
136     // Increment the caret position. If this is not done, the caret is left at
137     // the end of the field as a result of the replacing of contents.
138     field.selectionStart = caret + 1;
139     field.selectionEnd = caret + 1;
142 interactive(
143   "transpose-chars",
144   "Interchange characters around point, moving forward one character.",
145   function (I) call_on_focused_field(I, transpose_chars)
148 function meta_x (window, prefix, command, browser_object)
150     call_interactively({window: window,
151                         prefix_argument: prefix,
152                         browser_object: browser_object}, command);
154 interactive("execute-extended-command",
155             "Execute a Conkeror command specified in the minibuffer.",
156             function (I) {
157                 var prefix = I.P;
158                 var boc = I.browser_object;
159                 var prompt = "";
160                 if (boc)
161                     prompt += ' ['+boc.name+']';
162                 if (prefix !== null && prefix !== undefined) {
163                     if (typeof prefix == "object")
164                         prompt += prefix[0] == 4 ? " C-u" : " "+prefix[0];
165                     else
166                         prompt += " "+prefix;
167                 }
168                 meta_x(I.window, I.P,
169                        (yield I.minibuffer.read_command(
170                            $prompt = "M-x" + prompt)),
171                        boc);
172             });
174 /// built in commands
175 // see: http://www.xulplanet.com/tutorials/xultu/commandupdate.html
177 // Performs a command on a browser buffer content area
180 define_builtin_commands(
181     "",
182     function (I, command) {
183         var buffer = I.buffer;
184         try {
185             buffer.do_command(command);
186         } catch (e) {
187             /* Ignore exceptions */
188         }
189     },
190     function (I) {
191         I.buffer.mark_active = !I.buffer.mark_active;
192     },
193     function (I) I.buffer.mark_active,
194     false
197 define_builtin_commands(
198     "caret-",
199     function (I, command) {
200         var buffer = I.buffer;
201         try {
202             buffer.do_command(command);
203         } catch (e) {
204             /* Ignore exceptions */
205         }
206     },
207     function (I) {
208         I.buffer.mark_active = !I.buffer.mark_active;
209     },
210     function (I) I.buffer.mark_active,
211     'caret');
213 function get_link_text()
215     var e = document.commandDispatcher.focusedElement;
216     if (e && e.getAttribute("href")) {
217         return e.getAttribute("href");
218     }
219     return null;
224 function copy_email_address (loc)
226     // Copy the comma-separated list of email addresses only.
227     // There are other ways of embedding email addresses in a mailto:
228     // link, but such complex parsing is beyond us.
229     var qmark = loc.indexOf( "?" );
230     var addresses;
232     if ( qmark > 7 ) {                   // 7 == length of "mailto:"
233         addresses = loc.substring( 7, qmark );
234     } else {
235         addresses = loc.substr( 7 );
236     }
238     //XXX: the original code, which we got from firefox, unescapes the string
239     //     using the current character set.  To do this in conkeror, we
240     //     *should* use an interactive method that gives us the character set,
241     //     rather than fetching it by side-effect.
243     //     // Let's try to unescape it using a character set
244     //     // in case the address is not ASCII.
245     //     try {
246     //         var characterSet = this.target.ownerDocument.characterSet;
247     //         const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
248     //             .getService(Components.interfaces.nsITextToSubURI);
249     //         addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
250     //     }
251     //     catch(ex) {
252     //         // Do nothing.
253     //     }
255     writeToClipboard(addresses);
256     message("Copied '" + addresses + "'");
258 interactive("copy-email-address", copy_email_address, ['focused_link_url']);
261 /* FIXME: fix this command */
263 interactive("source",
264             "Load a JavaScript file.",
265             function (fo) { load_rc (fo.path); }, [['f', function (a) { return "Source File: "; }, null, "source"]]);
267 function reinit (window, fn)
269     try {
270         load_rc (fn);
271         window.minibuffer.message ("Loaded: " + fn);
272     } catch (e) {
273         window.minibuffer.message ("Failed to load: "+fn);
274     }
277 interactive ("reinit",
278              "Reload the Conkeror rc file.",
279              function (I) {
280                  reinit(I.window, get_pref("conkeror.rcfile"));
281              });
283 interactive("help-page", "Open the Conkeror help page.",
284             "find-url-new-buffer",
285             $browser_object = "chrome://conkeror-help/content/help.html");
287 interactive("help-with-tutorial", "Open the Conkeror tutorial.",
288             "find-url-new-buffer",
289             $browser_object = "chrome://conkeror-help/content/tutorial.html");
291 function univ_arg_to_number(prefix, default_value)
293     if (prefix == null) {
294         if (default_value == null)
295             return 1;
296         else
297             return default_value;
298     }
299     if (typeof prefix == "object")
300         return prefix[0];
301     return prefix;
304 function eval_expression(window, s)
306     // eval in the global scope.
308     // In addition, the following variables are available:
309     // var window;
310     var buffer = window.buffers.current;
311     var result = eval(s);
312     if (result !== undefined) {
313         window.minibuffer.message(String(result));
314     }
316 interactive("eval-expression",
317             "Evaluate JavaScript statements.",
318             function (I) {
319                 eval_expression(
320                     I.window,
321                     (yield I.minibuffer.read($prompt = "Eval:",
322                                              $history = "eval-expression",
323                                              $completer = javascript_completer(I.buffer))));
324             });
327 function show_extension_manager () {
328     return conkeror.window_watcher.openWindow (
329         null,
330         "chrome://mozapps/content/extensions/extensions.xul?type=extensions",
331         "ExtensionsWindow",
332         "resizable=yes,dialog=no",
333         null);
335 interactive("extensions",
336             "Open the extensions manager in a new window.",
337             show_extension_manager);
339 function print_buffer(buffer)
341     buffer.top_frame.print();
343 interactive("print-buffer",
344             "Print the currently loaded page.",
345             function (I) {print_buffer(I.buffer);});
347 function view_partial_source (window, charset, selection) {
348     if (charset) { charset = "charset=" + charset; }
349     window.window.openDialog("chrome://global/content/viewPartialSource.xul",
350                             "_blank", "scrollbars,resizable,chrome,dialog=no",
351                             null, charset, selection, 'selection');
353 //interactive ('view-partial-source', view_partial_source, I.current_window, I.content_charset, I.content_selection);
356 function  view_mathml_source (window, charset, target) {
357     if (charset) { charset = "charset=" + charset; }
358     window.window.openDialog("chrome://global/content/viewPartialSource.xul",
359                             "_blank", "scrollbars,resizable,chrome,dialog=no",
360                             null, charset, target, 'mathml');
364 function send_key_as_event (window, element, combo) {
365     var split = unformat_key_combo(combo);
366     var event = window.document.createEvent("KeyboardEvent");
367     event.initKeyEvent(
368         "keypress",
369         true,
370         true,
371         null,
372         split.ctrlKey,
373         split.altKey,
374         split.shiftKey,
375         split.metaKey,
376         split.keyCode,
377         split.charCode);
378     if (element) {
379         return element.dispatchEvent (event);
380     } else {
381         return window.dispatchEvent (event);
382     }
384 interactive (
385     "send-ret",
386     null,
387     function (I) {
388         send_key_as_event(I.window, I.buffer.focused_element, "return");
389     });
391 function ensure_content_focused(buffer) {
392     var foc = buffer.focused_frame_or_null;
393     if (!foc)
394         buffer.top_frame.focus();
396 interactive("ensure-content-focused", "Ensure that the content document has focus.",
397             function (I) { ensure_content_focused(I.buffer); });
399 function network_set_online_status (status) {
400     status = !status;
401     io_service.manageOfflineStatus = false;
402     io_service.offline = status;
405 interactive("network-go-online", "Work online.",
406             function (I) {network_set_online_status (true);});
407 interactive("network-go-offline", "Work offline.",
408             function (I) {network_set_online_status (false);});
411 interactive("submit-form",
412             "Submit the form to which the focused element belongs.",
413            function (I) {
414                var el = I.buffer.focused_element.parentNode;
415                while (el && el.tagName != "FORM")
416                    el = el.parentNode;
417                if (el)
418                    el.submit();
419            });
422  * Browser Object Commands
423  */
424 interactive("follow", null,
425             alternates(follow, follow_new_buffer, follow_new_window),
426             $browser_object = browser_object_links);
428 interactive("follow-top", null,
429             alternates(follow_top, follow_current_frame),
430             $browser_object = browser_object_frames,
431             $prompt = "Follow");
433 interactive("follow-new-buffer",
434             "Follow a link in a new buffer",
435             alternates(follow_new_buffer, follow_new_window),
436             $browser_object = browser_object_links,
437             $prompt = "Follow");
439 interactive("follow-new-buffer-background",
440             "Follow a link in a new buffer in the background",
441             alternates(follow_new_buffer_background, follow_new_window),
442             $browser_object = browser_object_links,
443             $prompt = "Follow");
445 interactive("follow-new-window",
446             "Follow a link in a new window",
447             follow_new_window,
448             $browser_object = browser_object_links,
449             $prompt = "Follow");
451 interactive("find-url", "Open a URL in the current buffer",
452             alternates(follow_current_buffer, follow_new_buffer, follow_new_window),
453             $browser_object = browser_object_url);
455 interactive("find-url-new-buffer",
456             "Open a URL in a new buffer",
457             alternates(follow_new_buffer, follow_new_window),
458             $browser_object = browser_object_url,
459             $prompt = "Find url");
461 interactive("find-url-new-window", "Open a URL in a new window",
462             follow_new_window,
463             $browser_object = browser_object_url,
464             $prompt = "Find url");
466 interactive("find-alternate-url", "Edit the current URL in the minibuffer",
467             "find-url",
468             $browser_object =
469                 define_browser_object_class(
470                     "alternate-url", null, null,
471                     function (I, prompt) {
472                         check_buffer (I.buffer, content_buffer);
473                         var result = yield I.buffer.window.minibuffer.read_url (
474                             $prompt = prompt,
475                             $initial_value = I.buffer.display_URI_string);
476                         yield co_return (result);
477                     }),
478             $prompt = "Find url");
481 interactive("go-up", "Go to the parent directory of the current URL",
482             "find-url",
483             $browser_object =
484                 define_browser_object_class(
485                     "up-url", null, null,
486                     function (I, prompt) {
487                         check_buffer (I.buffer, content_buffer);
488                         var up = compute_url_up_path (I.buffer.current_URI.spec);
489                         return I.buffer.current_URI.resolve (up);
490                     }));
492 interactive("make-window",
493             "Make a new window with the homepage.",
494             follow_new_window,
495             $browser_object = function () { return homepage; });
498 interactive("focus", null,
499             function (I) {
500                 var element = yield read_browser_object(I);
501                 browser_element_focus(I.buffer, element);
502             },
503             $browser_object = browser_object_frames);
505 interactive("save", null, function (I) {
506     var element = yield read_browser_object(I);
508     var spec = element_get_load_spec(element);
509     if (spec == null)
510         throw interactive_error("Element has no associated URI");
512     var panel;
513     panel = create_info_panel(I.window, "download-panel",
514                               [["downloading",
515                                 element_get_operation_label(element, "Saving"),
516                                 load_spec_uri_string(spec)],
517                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
519     try {
520         var file = yield I.minibuffer.read_file_check_overwrite(
521             $prompt = "Save as:",
522             $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
523             $history = "save");
525     } finally {
526         panel.destroy();
527     }
529     save_uri(spec, file,
530              $buffer = I.buffer,
531              $use_cache = false);
533            $browser_object = browser_object_links);
536 interactive("copy", null,
537             function (I) {
538                 var element = yield read_browser_object(I);
539                 browser_element_copy(I.buffer, element);
540             },
541             $browser_object = browser_object_links);
543 interactive("paste-url", "Open a URL from the clipboard in the current buffer.",
544             alternates(follow_current_buffer, follow_new_buffer, follow_new_window),
545             $browser_object = browser_object_pasteurl);
547 interactive("paste-url-new-buffer", "Open a URL from the clipboard in a new buffer.",
548             alternates(follow_new_buffer, follow_new_window),
549             $browser_object = browser_object_pasteurl);
551 interactive("paste-url-new-window", "Open a URL from the clipboard in a new window.",
552             follow_new_window,
553             $browser_object = browser_object_pasteurl);
555 interactive("view-source", null,
556             alternates(view_source, view_source_new_buffer, view_source_new_window),
557             $browser_object = browser_object_frames);
559 interactive("shell-command-on-url", null, function (I) {
560     var cwd = I.cwd;
561     var element = yield read_browser_object(I);
562     var spec = element_get_load_spec(element);
563     if (spec == null)
564         throw interactive_error("Unable to obtain URI from element");
566     var uri = load_spec_uri_string(spec);
568     var panel;
569     panel = create_info_panel(I.window, "download-panel",
570                               [["downloading",
571                                 element_get_operation_label(element, "Running on", "URI"),
572                                 load_spec_uri_string(spec)],
573                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
575     try {
576         var cmd = yield I.minibuffer.read_shell_command(
577             $cwd = cwd,
578             $initial_value = load_spec_default_shell_command(spec));
579     } finally {
580         panel.destroy();
581     }
583     shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
585             $browser_object = browser_object_url,
586             $prompt = "Shell command");
589 interactive("shell-command-on-file", null, function (I) {
590     var cwd = I.cwd;
591     var element = yield read_browser_object(I);
593     var spec = element_get_load_spec(element);
594     if (spec == null)
595         throw interactive_error("Unable to obtain URI from element");
597     var uri = load_spec_uri_string(spec);
599     var panel;
600     panel = create_info_panel(I.window, "download-panel",
601                               [["downloading",
602                                 element_get_operation_label(element, "Running on"),
603                                 load_spec_uri_string(spec)],
604                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
606     try {
608         var cmd = yield I.minibuffer.read_shell_command(
609             $cwd = cwd,
610             $initial_value = load_spec_default_shell_command(spec));
611     } finally {
612         panel.destroy();
613     }
615     /* FIXME: specify cwd as well */
616     yield browser_element_shell_command(I.buffer, element, cmd);
618             $browser_object = browser_object_links,
619             $prompt = "Shell command");
621 interactive("bookmark", null, function (I) {
622     var element = yield read_browser_object(I);
623     var spec = element_get_load_spec(element);
624     if (!spec)
625         throw interactive_error("Element has no associated URI");
626     var uri_string = load_spec_uri_string(spec);
627     var panel;
628     panel = create_info_panel(I.window, "bookmark-panel",
629                               [["bookmarking",
630                                 element_get_operation_label(element, "Bookmarking"),
631                                 uri_string]]);
632     try {
633         var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
634     } finally {
635         panel.destroy();
636     }
637     add_bookmark(uri_string, title);
638     I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
640             $browser_object = browser_object_frames);
642 interactive("save-page", null, function (I) {
643     check_buffer(I.buffer, content_buffer);
644     var element = yield read_browser_object(I);
645     var spec = element_get_load_spec(element);
646     if (!spec || !load_spec_document(spec))
647         throw interactive_error("Element is not associated with a document.");
648     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
650     var panel;
651     panel = create_info_panel(I.window, "download-panel",
652                               [["downloading",
653                                 element_get_operation_label(element, "Saving"),
654                                 load_spec_uri_string(spec)],
655                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
657     try {
658         var file = yield I.minibuffer.read_file_check_overwrite(
659             $prompt = "Save page as:",
660             $history = "save",
661             $initial_value = suggested_path);
662     } finally {
663         panel.destroy();
664     }
666     save_uri(spec, file, $buffer = I.buffer);
668             $browser_object = browser_object_frames);
670 interactive("save-page-as-text", null, function (I) {
671     check_buffer(I.buffer, content_buffer);
672     var element = yield read_browser_object(I);
673     var spec = element_get_load_spec(element);
674     var doc;
675     if (!spec || !(doc = load_spec_document(spec)))
676         throw interactive_error("Element is not associated with a document.");
677     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
679     var panel;
680     panel = create_info_panel(I.window, "download-panel",
681                               [["downloading",
682                                 element_get_operation_label(element, "Saving", "as text"),
683                                 load_spec_uri_string(spec)],
684                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
686     try {
687         var file = yield I.minibuffer.read_file_check_overwrite(
688             $prompt = "Save page as text:",
689             $history = "save",
690             $initial_value = suggested_path);
691     } finally {
692         panel.destroy();
693     }
695     save_document_as_text(doc, file, $buffer = I.buffer);
697             $browser_object = browser_object_frames);
699 interactive("save-page-complete", null, function (I) {
700     check_buffer(I.buffer, content_buffer);
701     var element = yield read_browser_object(I);
702     var spec = element_get_load_spec(element);
703     var doc;
704     if (!spec || !(doc = load_spec_document(spec)))
705         throw interactive_error("Element is not associated with a document.");
706     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
708     var panel;
709     panel = create_info_panel(I.window, "download-panel",
710                               [["downloading",
711                                 element_get_operation_label(element, "Saving complete"),
712                                 load_spec_uri_string(spec)],
713                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
715     try {
716         var file = yield I.minibuffer.read_file_check_overwrite(
717             $prompt = "Save page complete:",
718             $history = "save",
719             $initial_value = suggested_path);
720         // FIXME: use proper read function
721         var dir = yield I.minibuffer.read_file(
722             $prompt = "Data Directory:",
723             $history = "save",
724             $initial_value = file.path + ".support");
725     } finally {
726         panel.destroy();
727     }
729     save_document_complete(doc, file, dir, $buffer = I.buffer);
731             $browser_object = browser_object_frames);
734 function view_as_mime_type (I, target) {
735     I.target = target;
736     var element = yield read_browser_object(I);
737     var spec = element_get_load_spec(element);
739     if (target == null)
740         target = FOLLOW_CURRENT_FRAME;
742     if (!spec)
743         throw interactive_error("Element is not associated with a URI");
745     if (!can_override_mime_type_for_uri(load_spec_uri(spec)))
746         throw interactive_error("Overriding the MIME type is not currently supported for non-HTTP URLs.");
748     var panel;
750     var mime_type = load_spec_mime_type(spec);
751     panel = create_info_panel(I.window, "download-panel",
752                               [["downloading",
753                                 element_get_operation_label(element, "View in browser"),
754                                 load_spec_uri_string(spec)],
755                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
758     try {
759         let suggested_type = mime_type;
760         if (gecko_viewable_mime_type_list.indexOf(suggested_type) == -1)
761             suggested_type = "text/plain";
762         mime_type = yield I.minibuffer.read_gecko_viewable_mime_type(
763             $prompt = "View internally as",
764             $initial_value = suggested_type,
765             $select);
766         override_mime_type_for_next_load(load_spec_uri(spec), mime_type);
767         browser_object_follow(I.buffer, target, spec);
768     } finally {
769         panel.destroy();
770     }
772 function view_as_mime_type_new_buffer (I) {
773     yield view_as_mime_type(I, OPEN_NEW_BUFFER);
775 function view_as_mime_type_new_window (I) {
776     yield view_as_mime_type(I, OPEN_NEW_WINDOW);
778 interactive("view-as-mime-type",
779             "Display a browser object in the browser using the specified MIME type.",
780             alternates(view_as_mime_type,
781                        view_as_mime_type_new_buffer,
782                        view_as_mime_type_new_window));