Add facility to allow user functions to transform typed URLs.
[conkeror.git] / modules / commands.js
blob75ae1c5e9bcd3e635fccc20c3ab020aaa7b33c44
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);
109 interactive(
110   "open-line",
111   "If there is an active region, replace is with a newline, otherwise just " +
112   "insert a newline. In both cases leave point before the inserted newline.",
113   function (I) call_on_focused_field(I, open_line)
116 function meta_x (window, prefix, command, browser_object)
118     call_interactively({window: window,
119                         prefix_argument: prefix,
120                         browser_object: browser_object}, command);
122 interactive("execute-extended-command",
123             "Execute a Conkeror command specified in the minibuffer.",
124             function (I) {
125                 var prefix = I.P;
126                 var boc = I.browser_object;
127                 var prompt = "";
128                 if (boc)
129                     prompt += ' ['+boc.name+']';
130                 if (prefix !== null && prefix !== undefined) {
131                     if (typeof prefix == "object")
132                         prompt += prefix[0] == 4 ? " C-u" : " "+prefix[0];
133                     else
134                         prompt += " "+prefix;
135                 }
136                 meta_x(I.window, I.P,
137                        (yield I.minibuffer.read_command(
138                            $prompt = "M-x" + prompt)),
139                        boc);
140             });
142 /// built in commands
143 // see: http://www.xulplanet.com/tutorials/xultu/commandupdate.html
145 // Performs a command on a browser buffer content area
148 define_builtin_commands(
149     "",
150     function (I, command) {
151         var buffer = I.buffer;
152         try {
153             buffer.do_command(command);
154         } catch (e) {
155             /* Ignore exceptions */
156         }
157     },
158     function (I) {
159         I.buffer.mark_active = !I.buffer.mark_active;
160     },
161     function (I) I.buffer.mark_active,
162     false
165 define_builtin_commands(
166     "caret-",
167     function (I, command) {
168         var buffer = I.buffer;
169         try {
170             buffer.do_command(command);
171         } catch (e) {
172             /* Ignore exceptions */
173         }
174     },
175     function (I) {
176         I.buffer.mark_active = !I.buffer.mark_active;
177     },
178     function (I) I.buffer.mark_active,
179     'caret');
181 function get_link_text()
183     var e = document.commandDispatcher.focusedElement;
184     if (e && e.getAttribute("href")) {
185         return e.getAttribute("href");
186     }
187     return null;
192 function copy_email_address (loc)
194     // Copy the comma-separated list of email addresses only.
195     // There are other ways of embedding email addresses in a mailto:
196     // link, but such complex parsing is beyond us.
197     var qmark = loc.indexOf( "?" );
198     var addresses;
200     if ( qmark > 7 ) {                   // 7 == length of "mailto:"
201         addresses = loc.substring( 7, qmark );
202     } else {
203         addresses = loc.substr( 7 );
204     }
206     //XXX: the original code, which we got from firefox, unescapes the string
207     //     using the current character set.  To do this in conkeror, we
208     //     *should* use an interactive method that gives us the character set,
209     //     rather than fetching it by side-effect.
211     //     // Let's try to unescape it using a character set
212     //     // in case the address is not ASCII.
213     //     try {
214     //         var characterSet = this.target.ownerDocument.characterSet;
215     //         const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
216     //             .getService(Components.interfaces.nsITextToSubURI);
217     //         addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
218     //     }
219     //     catch(ex) {
220     //         // Do nothing.
221     //     }
223     writeToClipboard(addresses);
224     message("Copied '" + addresses + "'");
226 interactive("copy-email-address", copy_email_address, ['focused_link_url']);
229 /* FIXME: fix this command */
231 interactive("source",
232             "Load a JavaScript file.",
233             function (fo) { load_rc (fo.path); }, [['f', function (a) { return "Source File: "; }, null, "source"]]);
235 function reinit (window, fn)
237     try {
238         load_rc (fn);
239         window.minibuffer.message ("Loaded: " + fn);
240     } catch (e) {
241         window.minibuffer.message ("Failed to load: "+fn);
242     }
245 interactive ("reinit",
246              "Reload the Conkeror rc file.",
247              function (I) {
248                  reinit(I.window, get_pref("conkeror.rcfile"));
249              });
251 interactive("help-page", "Open the Conkeror help page.",
252             "find-url-new-buffer",
253             $browser_object = "chrome://conkeror/content/help.html");
255 interactive("help-with-tutorial", "Open the Conkeror tutorial.",
256             "find-url-new-buffer",
257             $browser_object = "chrome://conkeror/content/tutorial.html");
259 function univ_arg_to_number(prefix, default_value)
261     if (prefix == null) {
262         if (default_value == null)
263             return 1;
264         else
265             return default_value;
266     }
267     if (typeof prefix == "object")
268         return prefix[0];
269     return prefix;
272 function eval_expression(window, s)
274     // eval in the global scope.
276     // In addition, the following variables are available:
277     // var window;
278     var buffer = window.buffers.current;
279     var result = eval(s);
280     if (result !== undefined) {
281         window.minibuffer.message(String(result));
282     }
284 interactive("eval-expression",
285             "Evaluate JavaScript statements.",
286             function (I) {
287                 eval_expression(
288                     I.window,
289                     (yield I.minibuffer.read($prompt = "Eval:",
290                                              $history = "eval-expression",
291                                              $completer = javascript_completer(I.buffer))));
292             });
295 function show_extension_manager () {
296     return conkeror.window_watcher.openWindow (
297         null,
298         "chrome://mozapps/content/extensions/extensions.xul?type=extensions",
299         "ExtensionsWindow",
300         "resizable=yes,dialog=no",
301         null);
303 interactive("extensions",
304             "Open the extensions manager in a new window.",
305             show_extension_manager);
307 function print_buffer(buffer)
309     buffer.top_frame.print();
311 interactive("print-buffer",
312             "Print the currently loaded page.",
313             function (I) {print_buffer(I.buffer);});
315 function view_partial_source (window, charset, selection) {
316     if (charset) { charset = "charset=" + charset; }
317     window.window.openDialog("chrome://global/content/viewPartialSource.xul",
318                             "_blank", "scrollbars,resizable,chrome,dialog=no",
319                             null, charset, selection, 'selection');
321 //interactive ('view-partial-source', view_partial_source, I.current_window, I.content_charset, I.content_selection);
324 function  view_mathml_source (window, charset, target) {
325     if (charset) { charset = "charset=" + charset; }
326     window.window.openDialog("chrome://global/content/viewPartialSource.xul",
327                             "_blank", "scrollbars,resizable,chrome,dialog=no",
328                             null, charset, target, 'mathml');
332 function send_key_as_event (window, element, key) {
333     key = kbd (key)[0];
334     var event = window.document.createEvent ("KeyboardEvent");
335     event.initKeyEvent (
336         "keypress",
337         true,
338         true,
339         null,
340         key.modifiers & MOD_CTRL, // ctrl
341         key.modifiers & MOD_META, // alt
342         key.modifiers & MOD_SHIFT, // shift
343         key.modifiers & MOD_META, // meta
344         key.keyCode,
345         null);    // charcode
346     // bit of a hack here.. we have to fake a keydown event for conkeror
347     window.keyboard.last_key_down_event = copy_event (event);
348     if (element) {
349         return element.dispatchEvent (event);
350     } else {
351         return window.dispatchEvent (event);
352     }
354 interactive (
355     "send-ret",
356     null,
357     function (I) {
358         send_key_as_event (I.window, I.buffer.focused_element, "return");
359     });
361 function ensure_content_focused(buffer) {
362     var foc = buffer.focused_frame_or_null;
363     if (!foc)
364         buffer.top_frame.focus();
366 interactive("ensure-content-focused", "Ensure that the content document has focus.",
367             function (I) { ensure_content_focused(I.buffer); });
369 function network_set_online_status (status) {
370     status = !status;
371     io_service.manageOfflineStatus = false;
372     io_service.offline = status;
375 interactive("network-go-online", "Work online.",
376             function (I) {network_set_online_status (true);});
377 interactive("network-go-offline", "Work offline.",
378             function (I) {network_set_online_status (false);});
381 interactive("submit-form", null,
382            function (I) {
383                var el = I.buffer.focused_element.parentNode;
384                while (el && el.tagName != "FORM")
385                    el = el.parentNode;
386                if (el)
387                    el.submit();
388            });
391  * Browser Object Commands
392  */
393 interactive("follow", null,
394             alternates(follow, follow_new_buffer, follow_new_window),
395             $browser_object = browser_object_links);
397 interactive("follow-top", null,
398             alternates(follow_top, follow_current_frame),
399             $browser_object = browser_object_frames,
400             $prompt = "Follow");
402 interactive("follow-new-buffer",
403             "Follow a link in a new buffer",
404             alternates(follow_new_buffer, follow_new_window),
405             $browser_object = browser_object_links,
406             $prompt = "Follow");
408 interactive("follow-new-buffer-background",
409             "Follow a link in a new buffer in the background",
410             alternates(follow_new_buffer_background, follow_new_window),
411             $browser_object = browser_object_links,
412             $prompt = "Follow");
414 interactive("follow-new-window",
415             "Follow a link in a new window",
416             follow_new_window,
417             $browser_object = browser_object_links,
418             $prompt = "Follow");
420 interactive("find-url", "Open a URL in the current buffer",
421             alternates(follow_current_buffer, follow_new_buffer, follow_new_window),
422             $browser_object = browser_object_url);
424 interactive("find-url-new-buffer",
425             "Open a URL in a new buffer",
426             alternates(follow_new_buffer, follow_new_window),
427             $browser_object = browser_object_url,
428             $prompt = "Find url");
430 interactive("find-url-new-window", "Open a URL in a new window",
431             follow_new_window,
432             $browser_object = browser_object_url,
433             $prompt = "Find url");
435 interactive("find-alternate-url", "Edit the current URL in the minibuffer",
436             "find-url",
437             $browser_object =
438                 define_browser_object_class(
439                     "alternate-url", null, null,
440                     function (I, prompt) {
441                         check_buffer (I.buffer, content_buffer);
442                         var result = yield I.buffer.window.minibuffer.read_url (
443                             $prompt = prompt,
444                             $initial_value = I.buffer.display_URI_string);
445                         yield co_return (result);
446                     }),
447             $prompt = "Find url");
450 interactive("go-up", "Go to the parent directory of the current URL",
451             "find-url",
452             $browser_object =
453                 define_browser_object_class(
454                     "up-url", null, null,
455                     function (I, prompt) {
456                         check_buffer (I.buffer, content_buffer);
457                         var up = compute_url_up_path (I.buffer.current_URI.spec);
458                         return I.buffer.current_URI.resolve (up);
459                     }));
461 interactive("make-window",
462             "Make a new window with the homepage.",
463             follow_new_window,
464             $browser_object = function () { return homepage; });
467 interactive("focus", null,
468             function (I) {
469                 var element = yield read_browser_object(I);
470                 browser_element_focus(I.buffer, element);
471             },
472             $browser_object = browser_object_frames);
474 interactive("save", null, function (I) {
475     var element = yield read_browser_object(I);
477     var spec = element_get_load_spec(element);
478     if (spec == null)
479         throw interactive_error("Element has no associated URI");
481     var panel;
482     panel = create_info_panel(I.window, "download-panel",
483                               [["downloading",
484                                 element_get_operation_label(element, "Saving"),
485                                 load_spec_uri_string(spec)],
486                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
488     try {
489         var file = yield I.minibuffer.read_file_check_overwrite(
490             $prompt = "Save as:",
491             $initial_value = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer),
492             $history = "save");
494     } finally {
495         panel.destroy();
496     }
498     save_uri(spec, file,
499              $buffer = I.buffer,
500              $use_cache = false);
502            $browser_object = browser_object_links);
505 interactive("copy", null,
506             function (I) {
507                 var element = yield read_browser_object(I);
508                 browser_element_copy(I.buffer, element);
509             },
510             $browser_object = browser_object_links);
512 interactive("paste-url", "Open a URL from the clipboard in the current buffer.",
513             alternates(follow_current_buffer, follow_new_buffer, follow_new_window),
514             $browser_object = browser_object_pasteurl);
516 interactive("paste-url-new-buffer", "Open a URL from the clipboard in a new buffer.",
517             alternates(follow_new_buffer, follow_new_window),
518             $browser_object = browser_object_pasteurl);
520 interactive("paste-url-new-window", "Open a URL from the clipboard in a new window.",
521             follow_new_window,
522             $browser_object = browser_object_pasteurl);
524 interactive("view-source", null,
525             alternates(view_source, view_source_new_buffer, view_source_new_window),
526             $browser_object = browser_object_frames);
528 interactive("shell-command-on-url", null, function (I) {
529     var cwd = I.cwd;
530     var element = yield read_browser_object(I);
531     var spec = element_get_load_spec(element);
532     if (spec == null)
533         throw interactive_error("Unable to obtain URI from element");
535     var uri = load_spec_uri_string(spec);
537     var panel;
538     panel = create_info_panel(I.window, "download-panel",
539                               [["downloading",
540                                 element_get_operation_label(element, "Running on", "URI"),
541                                 load_spec_uri_string(spec)],
542                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
544     try {
545         var cmd = yield I.minibuffer.read_shell_command(
546             $cwd = cwd,
547             $initial_value = load_spec_default_shell_command(spec));
548     } finally {
549         panel.destroy();
550     }
552     shell_command_with_argument_blind(cmd, uri, $cwd = cwd);
554             $browser_object = browser_object_url,
555             $prompt = "Shell command");
558 interactive("shell-command-on-file", null, function (I) {
559     var cwd = I.cwd;
560     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"),
572                                 load_spec_uri_string(spec)],
573                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
575     try {
577         var cmd = yield I.minibuffer.read_shell_command(
578             $cwd = cwd,
579             $initial_value = load_spec_default_shell_command(spec));
580     } finally {
581         panel.destroy();
582     }
584     /* FIXME: specify cwd as well */
585     yield browser_element_shell_command(I.buffer, element, cmd);
587             $browser_object = browser_object_links,
588             $prompt = "Shell command");
590 interactive("bookmark", null, function (I) {
591     var element = yield read_browser_object(I);
592     var spec = element_get_load_spec(element);
593     if (!spec)
594         throw interactive_error("Element has no associated URI");
595     var uri_string = load_spec_uri_string(spec);
596     var panel;
597     panel = create_info_panel(I.window, "bookmark-panel",
598                               [["bookmarking",
599                                 element_get_operation_label(element, "Bookmarking"),
600                                 uri_string]]);
601     try {
602         var title = yield I.minibuffer.read($prompt = "Bookmark with title:", $initial_value = load_spec_title(spec) || "");
603     } finally {
604         panel.destroy();
605     }
606     add_bookmark(uri_string, title);
607     I.minibuffer.message("Added bookmark: " + uri_string + " - " + title);
609             $browser_object = browser_object_frames);
611 interactive("save-page", null, function (I) {
612     check_buffer(I.buffer, content_buffer);
613     var element = yield read_browser_object(I);
614     var spec = element_get_load_spec(element);
615     if (!spec || !load_spec_document(spec))
616         throw interactive_error("Element is not associated with a document.");
617     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
619     var panel;
620     panel = create_info_panel(I.window, "download-panel",
621                               [["downloading",
622                                 element_get_operation_label(element, "Saving"),
623                                 load_spec_uri_string(spec)],
624                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
626     try {
627         var file = yield I.minibuffer.read_file_check_overwrite(
628             $prompt = "Save page as:",
629             $history = "save",
630             $initial_value = suggested_path);
631     } finally {
632         panel.destroy();
633     }
635     save_uri(spec, file, $buffer = I.buffer);
637             $browser_object = browser_object_frames);
639 interactive("save-page-as-text", null, function (I) {
640     check_buffer(I.buffer, content_buffer);
641     var element = yield read_browser_object(I);
642     var spec = element_get_load_spec(element);
643     var doc;
644     if (!spec || !(doc = load_spec_document(spec)))
645         throw interactive_error("Element is not associated with a document.");
646     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec, "txt"), I.buffer);
648     var panel;
649     panel = create_info_panel(I.window, "download-panel",
650                               [["downloading",
651                                 element_get_operation_label(element, "Saving", "as text"),
652                                 load_spec_uri_string(spec)],
653                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
655     try {
656         var file = yield I.minibuffer.read_file_check_overwrite(
657             $prompt = "Save page as text:",
658             $history = "save",
659             $initial_value = suggested_path);
660     } finally {
661         panel.destroy();
662     }
664     save_document_as_text(doc, file, $buffer = I.buffer);
666             $browser_object = browser_object_frames);
668 interactive("save-page-complete", null, function (I) {
669     check_buffer(I.buffer, content_buffer);
670     var element = yield read_browser_object(I);
671     var spec = element_get_load_spec(element);
672     var doc;
673     if (!spec || !(doc = load_spec_document(spec)))
674         throw interactive_error("Element is not associated with a document.");
675     var suggested_path = suggest_save_path_from_file_name(suggest_file_name(spec), I.buffer);
677     var panel;
678     panel = create_info_panel(I.window, "download-panel",
679                               [["downloading",
680                                 element_get_operation_label(element, "Saving complete"),
681                                 load_spec_uri_string(spec)],
682                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
684     try {
685         var file = yield I.minibuffer.read_file_check_overwrite(
686             $prompt = "Save page complete:",
687             $history = "save",
688             $initial_value = suggested_path);
689         // FIXME: use proper read function
690         var dir = yield I.minibuffer.read_file(
691             $prompt = "Data Directory:",
692             $history = "save",
693             $initial_value = file.path + ".support");
694     } finally {
695         panel.destroy();
696     }
698     save_document_complete(doc, file, dir, $buffer = I.buffer);
700             $browser_object = browser_object_frames);
703 function view_as_mime_type (I, target) {
704     I.target = target;
705     var element = yield read_browser_object(I);
706     var spec = element_get_load_spec(element);
708     if (target == null)
709         target = FOLLOW_CURRENT_FRAME;
711     if (!spec)
712         throw interactive_error("Element is not associated with a URI");
714     if (!can_override_mime_type_for_uri(load_spec_uri(spec)))
715         throw interactive_error("Overriding the MIME type is not currently supported for non-HTTP URLs.");
717     var panel;
719     var mime_type = load_spec_mime_type(spec);
720     panel = create_info_panel(I.window, "download-panel",
721                               [["downloading",
722                                 element_get_operation_label(element, "View in browser"),
723                                 load_spec_uri_string(spec)],
724                                ["mime-type", "Mime type:", load_spec_mime_type(spec)]]);
727     try {
728         let suggested_type = mime_type;
729         if (gecko_viewable_mime_type_list.indexOf(suggested_type) == -1)
730             suggested_type = "text/plain";
731         mime_type = yield I.minibuffer.read_gecko_viewable_mime_type(
732             $prompt = "View internally as",
733             $initial_value = suggested_type,
734             $select);
735         override_mime_type_for_next_load(load_spec_uri(spec), mime_type);
736         browser_object_follow(I.buffer, target, spec);
737     } finally {
738         panel.destroy();
739     }
741 function view_as_mime_type_new_buffer (I) {
742     yield view_as_mime_type(I, OPEN_NEW_BUFFER);
744 function view_as_mime_type_new_window (I) {
745     yield view_as_mime_type(I, OPEN_NEW_WINDOW);
747 interactive("view-as-mime-type",
748             "Display a browser object in the browser using the specified MIME type.",
749             alternates(view_as_mime_type,
750                        view_as_mime_type_new_buffer,
751                        view_as_mime_type_new_window));